blob: 29e24f933ad1c84b7902553166a9e705e89f51b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.jsp.core.internal.java;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jface.text.Position;
import org.eclipse.jst.jsp.core.internal.Logger;
/**
* <p>
* An implementation of IJSPTranslation.
* <br>
* This object that holds the java translation of
* a JSP file as well as a mapping of ranges from the translated Java to the JSP source,
* and mapping from JSP source back to the translated Java.
* </p>
*
* <p>
* You may also use JSPTranslation to do CompilationUnit-esque things such as:
* <ul>
* <li>code select (get java elements for jsp selection)</li>
* <li>reconcile</li>
* <li>get java regions for jsp selection</li>
* <li>get a JSP text edit based on a Java text edit</li>
* <li>determine if a java offset falls within a jsp:useBean range</li>
* <li>determine if a java offset falls within a jsp import statment</li>
* </ul>
* </p>
*
* @author pavery
*/
public class JSPTranslation implements IJSPTranslation {
// for debugging
private static final boolean DEBUG;
static {
String value= Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jsptranslation"); //$NON-NLS-1$
DEBUG= value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$
}
/** the name of the class (w/out extension) **/
private String fClassname = ""; //$NON-NLS-1$
private IJavaProject fJavaProject = null;
private HashMap fJava2JspMap = null;
private HashMap fJsp2JavaMap = null;
private HashMap fJava2JspImportsMap = null;
private HashMap fJava2JspUseBeanMap = null;
private HashMap fJava2JspIndirectMap = null;
private List fELProblems = null;
// don't want to hold onto model (via translator)
// all relevant info is extracted in the constructor.
//private JSPTranslator fTranslator = null;
private String fJavaText = ""; //$NON-NLS-1$
private String fJspText = ""; //$NON-NLS-1$
private ICompilationUnit fCompilationUnit = null;
private IProgressMonitor fProgressMonitor = null;
/** lock to synchronize access to the compilation unit **/
private byte[] fLock = null;
private String fMangledName;
private String fJspName;
private List fTranslationProblems;
private Collection fIncludedPaths;
public JSPTranslation(IJavaProject javaProj, JSPTranslator translator) {
fLock = new byte[0];
fJavaProject = javaProj;
//fTranslator = translator;
// can be null if it's an empty document (w/ NullJSPTranslation)
if(translator != null) {
fJavaText = translator.getTranslation().toString();
fJspText = translator.getJspText();
fClassname = translator.getClassname();
fJava2JspMap = translator.getJava2JspRanges();
fJsp2JavaMap = translator.getJsp2JavaRanges();
fJava2JspImportsMap = translator.getJava2JspImportRanges();
fJava2JspUseBeanMap = translator.getJava2JspUseBeanRanges();
fJava2JspIndirectMap = translator.getJava2JspIndirectRanges();
fELProblems = translator.getELProblems();
fTranslationProblems = translator.getTranslationProblems();
fIncludedPaths = translator.getIncludedPaths();
}
}
public IJavaProject getJavaProject() {
return fJavaProject;
}
/* (non-Javadoc)
* @see org.eclipse.jst.jsp.core.internal.java.IJSPTranslation#getJavaText()
*/
public String getJavaText() {
//return (fTranslator != null) ? fTranslator.getTranslation().toString() : ""; //$NON-NLS-1$
return fJavaText;
}
public String getJspText() {
//return (fTranslator != null) ? fTranslator.getJspText() : ""; //$NON-NLS-1$
return fJspText;
}
public String getJavaPath() {
// create if necessary
ICompilationUnit cu = getCompilationUnit();
return (cu != null) ? cu.getPath().toString() : ""; //$NON-NLS-1$
}
/**
*
* @return the corresponding Java offset for a give JSP offset
*/
public int getJavaOffset(int jspOffset) {
int result = -1;
int offsetInRange = 0;
Position jspPos, javaPos = null;
// iterate all mapped jsp ranges
Iterator it = fJsp2JavaMap.keySet().iterator();
while (it.hasNext()) {
jspPos = (Position) it.next();
// need to count the last position as included
if (!jspPos.includes(jspOffset) && !(jspPos.offset+jspPos.length == jspOffset))
continue;
offsetInRange = jspOffset - jspPos.offset;
javaPos = (Position) fJsp2JavaMap.get(jspPos);
if(javaPos != null)
result = javaPos.offset + offsetInRange;
else {
Logger.log(Logger.ERROR, "JavaPosition was null!" + jspOffset); //$NON-NLS-1$
}
break;
}
return result;
}
/**
*
* @return the corresponding JSP offset for a give Java offset
*/
public int getJspOffset(int javaOffset) {
int result = -1;
int offsetInRange = 0;
Position jspPos, javaPos = null;
// iterate all mapped java ranges
Iterator it = fJava2JspMap.keySet().iterator();
while (it.hasNext()) {
javaPos = (Position) it.next();
// need to count the last position as included
if (!javaPos.includes(javaOffset) && !(javaPos.offset+javaPos.length == javaOffset))
continue;
offsetInRange = javaOffset - javaPos.offset;
jspPos = (Position) fJava2JspMap.get(javaPos);
if(jspPos != null)
result = jspPos.offset + offsetInRange;
else {
Logger.log(Logger.ERROR, "jspPosition was null!" + javaOffset); //$NON-NLS-1$
}
break;
}
return result;
}
public Collection getIncludedPaths() {
return fIncludedPaths;
}
/**
*
* @return a map of Positions in the Java document to corresponding Positions in the JSP document
*/
public HashMap getJava2JspMap() {
return fJava2JspMap;
}
/**
*
* @return a map of Positions in the JSP document to corresponding Positions in the Java document
*/
public HashMap getJsp2JavaMap() {
return fJsp2JavaMap;
}
/**
* Checks if the specified java range covers more than one partition in the JSP file.
*
* <p>
* ex.
* <code>
* <%
* if(submit)
* {
* %>
* <p> print this...</p>
*
* <%
* }
* else
* {
* %>
* <p> print that...</p>
* <%
* }
* %>
* </code>
* </p>
*
* the if else statement above spans 3 JSP partitions, so it would return true.
* @param offset
* @param length
* @return <code>true</code> if the java code spans multiple JSP partitions, otherwise false.
*/
public boolean javaSpansMultipleJspPartitions(int javaOffset, int javaLength) {
HashMap java2jsp = getJava2JspMap();
int count = 0;
Iterator it = java2jsp.keySet().iterator();
Position javaRange = null;
while(it.hasNext()) {
javaRange = (Position)it.next();
if(javaRange.overlapsWith(javaOffset, javaLength))
count++;
if(count > 1)
return true;
}
return false;
}
/**
* Returns the Java positions for the given range in the Java document.
*
* @param offset
* @param length
* @return
*/
public Position[] getJavaRanges(int offset, int length) {
List results = new ArrayList();
Iterator it = getJava2JspMap().keySet().iterator();
Position p = null;
while(it.hasNext()) {
p = (Position)it.next();
if(p.overlapsWith(offset, length))
results.add(p);
}
return (Position[])results.toArray(new Position[results.size()]);
}
/**
* Indicates if the java Offset falls within the user import ranges
* @param javaOffset
* @return true if the java Offset falls within the user import ranges, otherwise false
*/
public boolean isImport(int javaOffset) {
return isInRanges(javaOffset, fJava2JspImportsMap);
}
/**
* Indicates if the java offset falls within the use bean ranges
* @param javaOffset
* @return true if the java offset falls within the user import ranges, otherwise false
*/
public boolean isUseBean(int javaOffset) {
return isInRanges(javaOffset, fJava2JspUseBeanMap);
}
/**
* @param javaPos
* @return
*/
public boolean isIndirect(int javaOffset) {
return isInRanges(javaOffset, fJava2JspIndirectMap, false);
}
private boolean isInRanges(int javaOffset, HashMap ranges) {
return isInRanges(javaOffset, ranges, true);
}
/**
* Tells you if the given offset is included in any of the ranges (Positions) passed in.
* includeEndOffset tells whether or not to include the end offset of each range in the test.
*
* @param javaOffset
* @param ranges
* @param includeEndOffset
* @return
*/
private boolean isInRanges(int javaOffset, HashMap ranges, boolean includeEndOffset) {
Iterator it = ranges.keySet().iterator();
while(it.hasNext()) {
Position javaPos = (Position)it.next();
// also include the start and end offset (only if requested)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=81687
if(javaPos.includes(javaOffset) || (includeEndOffset && javaPos.offset+javaPos.length == javaOffset))
return true;
}
return false;
}
/**
* Return the Java CompilationUnit associated with this JSPTranslation (this particular model)
* When using methods on the CU, it's reccomended to synchronize on the CU for reliable
* results.
*
* The method is synchronized to ensure that <code>createComplilationUnit</code> doesn't
* get entered 2 or more times simultaneously. A side effect of that is 2 compilation units
* can be created in the JavaModelManager, but we only hold one reference to it in
* fCompilationUnit. This would lead to a leak since only one instance of the CU is
* discarded in the <code>release()</code> method.
*
* @return a CompilationUnit representation of this JSPTranslation
*/
public ICompilationUnit getCompilationUnit() {
synchronized(fLock) {
try {
if (fCompilationUnit == null) {
fCompilationUnit = createCompilationUnit();
}
}
catch (JavaModelException jme) {
if(DEBUG)
Logger.logException("error creating JSP working copy... ", jme); //$NON-NLS-1$
}
}
return fCompilationUnit;
}
private String getMangledName() {
return fMangledName;
}
private void setMangledName(String mangledName) {
fMangledName = mangledName;
}
private String getJspName() {
return fJspName;
}
private void setJspName(String jspName) {
fJspName = jspName;
}
/**
* Replaces mangled (servlet) name with jsp file name.
*
* @param displayString
* @return
*/
public String fixupMangledName(String displayString) {
if(displayString == null)
return null;
if(getJspName() == null || getMangledName() == null) {
// names not set yet
initJspAndServletNames();
}
return displayString.replaceAll(getMangledName(), getJspName());
}
private void initJspAndServletNames() {
ICompilationUnit cu = getCompilationUnit();
if(cu != null) {
String cuName = null;
synchronized(cu) {
// set some names for fixing up mangled name in proposals
// set mangled (servlet) name
cuName = cu.getPath().lastSegment();
}
if(cuName != null) {
setMangledName(cuName.substring(0, cuName.lastIndexOf('.')));
// set name of jsp file
String unmangled = JSP2ServletNameUtil.unmangle(cuName);
setJspName(unmangled.substring(unmangled.lastIndexOf('/') + 1, unmangled.lastIndexOf('.')));
}
}
}
/**
* Originally from ReconcileStepForJava. Creates an ICompilationUnit from the contents of the JSP document.
*
* @return an ICompilationUnit from the contents of the JSP document
*/
private ICompilationUnit createCompilationUnit() throws JavaModelException {
IPackageFragment packageFragment = null;
IJavaElement je = getJavaProject();
if (je == null || !je.exists())
return null;
switch (je.getElementType()) {
case IJavaElement.PACKAGE_FRAGMENT :
je = je.getParent();
// fall through
case IJavaElement.PACKAGE_FRAGMENT_ROOT :
IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) je;
packageFragment = packageFragmentRoot.getPackageFragment(IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH);
break;
case IJavaElement.JAVA_PROJECT :
IJavaProject jProject = (IJavaProject) je;
if (!jProject.exists()) {
if(DEBUG) {
System.out.println("** Abort create working copy: cannot create working copy: JSP is not in a Java project"); //$NON-NLS-1$
}
return null;
}
packageFragmentRoot = null;
IPackageFragmentRoot[] packageFragmentRoots = jProject.getPackageFragmentRoots();
int i = 0;
while (i < packageFragmentRoots.length) {
if (!packageFragmentRoots[i].isArchive() && !packageFragmentRoots[i].isExternal()) {
packageFragmentRoot = packageFragmentRoots[i];
break;
}
i++;
}
if (packageFragmentRoot == null) {
if(DEBUG) {
System.out.println("** Abort create working copy: cannot create working copy: JSP is not in a Java project with source package fragment root"); //$NON-NLS-1$
}
return null;
}
packageFragment = packageFragmentRoot.getPackageFragment(IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH);
break;
default :
return null;
}
ICompilationUnit cu = packageFragment.getCompilationUnit(getClassname() + ".java").getWorkingCopy(getWorkingCopyOwner(), getProblemRequestor(), getProgressMonitor()); //$NON-NLS-1$
setContents(cu);
if(DEBUG) {
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); //$NON-NLS-1$
System.out.println("(+) JSPTranslation ["+ this + "] finished creating CompilationUnit: " + cu); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); //$NON-NLS-1$
}
return cu;
}
/**
*
* @return the problem requestor for the CompilationUnit in this JSPTranslation
*/
private JSPProblemRequestor getProblemRequestor() {
return CompilationUnitHelper.getInstance().getProblemRequestor();
}
/**
*
* @return the IWorkingCopyOwner for this CompilationUnit in this JSPTranslation
*/
public WorkingCopyOwner getWorkingCopyOwner() {
return CompilationUnitHelper.getInstance().getWorkingCopyOwner();
}
/**
*
* @return the progress monitor used in long operations (reconcile, creating the CompilationUnit...) in this JSPTranslation
*/
private IProgressMonitor getProgressMonitor() {
if (fProgressMonitor == null)
fProgressMonitor = new NullProgressMonitor();
return fProgressMonitor;
}
/**
*
* @return the List of problems collected during reconcile of the compilation unit
*/
public List getProblems() {
List problems = getProblemRequestor().getCollectedProblems();
if (problems != null) {
if (fTranslationProblems.isEmpty()) {
return problems;
}
List combinedProblems = new ArrayList(problems);
combinedProblems.addAll(fTranslationProblems);
return combinedProblems;
}
return fTranslationProblems;
}
/**
*
* @return the List of problems collected during reconcile of the compilation unit
*/
public List getELProblems() {
return fELProblems != null ? fELProblems : new ArrayList();
}
/**
* Must be set true in order for problems to be collected during reconcile.
* If set false, problems will be ignored during reconcile.
* @param collect
*/
public void setProblemCollectingActive(boolean collect) {
ICompilationUnit cu = getCompilationUnit();
if(cu != null) {
getProblemRequestor().setIsActive(collect);
}
}
/**
* Reconciles the compilation unit for this JSPTranslation
*/
public void reconcileCompilationUnit() {
ICompilationUnit cu = getCompilationUnit();
if (cu != null) {
try {
synchronized(cu) {
cu.makeConsistent(getProgressMonitor());
cu.reconcile(ICompilationUnit.NO_AST, true, getWorkingCopyOwner(), getProgressMonitor());
}
}
catch (JavaModelException e) {
Logger.logException(e);
}
}
}
/**
* Set contents of the compilation unit to the translated jsp text.
* @param the ICompilationUnit on which to set the buffer contents
*/
private void setContents(ICompilationUnit cu) {
if (cu == null)
return;
synchronized (cu) {
IBuffer buffer;
try {
buffer = cu.getBuffer();
}
catch (JavaModelException e) {
e.printStackTrace();
buffer = null;
}
if (buffer != null)
buffer.setContents(getJavaText());
}
}
/**
* Returns the IJavaElements corresponding to the JSP range in the JSP StructuredDocument
*
* @param jspStart staring offset in the JSP document
* @param jspEnd ending offset in the JSP document
* @return IJavaElements corresponding to the JSP selection
*/
public IJavaElement[] getElementsFromJspRange(int jspStart, int jspEnd) {
int javaPositionStart = getJavaOffset(jspStart);
int javaPositionEnd = getJavaOffset(jspEnd);
IJavaElement[] EMTPY_RESULT_SET = new IJavaElement[0];
IJavaElement[] result = EMTPY_RESULT_SET;
try {
ICompilationUnit cu = getCompilationUnit();
if (cu != null) {
synchronized (cu) {
int cuDocLength = cu.getBuffer().getLength();
int javaLength = javaPositionEnd - javaPositionStart;
if (cuDocLength > 0 && javaPositionStart >= 0 && javaLength >= 0 && javaPositionEnd < cuDocLength) {
result = cu.codeSelect(javaPositionStart, javaLength);
}
}
}
if (result == null || result.length == 0)
return EMTPY_RESULT_SET;
}
catch (JavaModelException x) {
Logger.logException(x);
}
return result;
}
public String getClassname() {
return fClassname;
}
/**
* Must discard compilation unit, or else they can leak in the JavaModelManager
*/
public void release() {
synchronized (fLock) {
if (fCompilationUnit != null) {
Job discarder = new WorkspaceJob(fClassname) {
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
try {
if (DEBUG) {
System.out.println("------------------------------------------------------------------"); //$NON-NLS-1$
System.out.println("(-) JSPTranslation [" + this + "] discarding CompilationUnit: " + fCompilationUnit); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println("------------------------------------------------------------------"); //$NON-NLS-1$
}
fCompilationUnit.discardWorkingCopy();
}
catch (JavaModelException e) {
// we're done w/ it anyway
}
return Status.OK_STATUS;
}
};
discarder.schedule();
}
}
}
}