| /******************************************************************************* |
| * Copyright (c) 2004 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.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| 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; |
| |
| 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(); |
| } |
| } |
| |
| 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; |
| } |
| |
| /** |
| * |
| * @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(); |
| return problems != null ? problems : new ArrayList(); |
| } |
| |
| /** |
| * |
| * @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) { |
| 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 |
| } |
| } |
| } |
| } |
| } |