| /******************************************************************************* |
| * Copyright (c) 2004, 2023 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Frits Jalvingh - contributions for bug 150794 |
| * Gregory Amerson - [bug 432978] better support for multiple AT_BEGIN variables |
| *******************************************************************************/ |
| package org.eclipse.jst.jsp.core.internal.java; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedReader; |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.Reader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import javax.servlet.jsp.tagext.VariableInfo; |
| |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.QualifiedName; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jst.jsp.core.internal.JSPCoreMessages; |
| import org.eclipse.jst.jsp.core.internal.Logger; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP12TLDNames; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDElementDeclaration; |
| import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache; |
| import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache.PropertyGroup; |
| import org.eclipse.jst.jsp.core.internal.contenttype.ServletAPIDescriptor; |
| import org.eclipse.jst.jsp.core.internal.provisional.JSP11Namespace; |
| import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace; |
| import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts; |
| import org.eclipse.jst.jsp.core.internal.taglib.CustomTag; |
| import org.eclipse.jst.jsp.core.internal.taglib.TaglibHelper; |
| import org.eclipse.jst.jsp.core.internal.taglib.TaglibHelperManager; |
| import org.eclipse.jst.jsp.core.internal.taglib.TaglibVariable; |
| import org.eclipse.jst.jsp.core.internal.util.FacetModuleCoreSupport; |
| import org.eclipse.jst.jsp.core.internal.util.ZeroStructuredDocumentRegion; |
| import org.eclipse.jst.jsp.core.jspel.IJSPELTranslator; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.html.core.internal.contentmodel.JSP20Namespace; |
| import org.eclipse.wst.sse.core.internal.FileBufferModelManager; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| import org.eclipse.wst.sse.core.utils.StringUtils; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNode; |
| import org.eclipse.wst.xml.core.internal.parser.ContextRegionContainer; |
| import org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMDocumentTracker; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| |
| import com.ibm.icu.text.MessageFormat; |
| import com.ibm.icu.util.StringTokenizer; |
| |
| /** |
| * Translates a JSP document into a HttpServlet subclass. Keeps two way mapping from |
| * Java translation to the original JSP source, which can be obtained through |
| * getJava2JspRanges() and getJsp2JavaRanges(). |
| */ |
| public class JSPTranslator implements Externalizable { |
| /** |
| * <p>This value should be incremented if any of the following methods change: |
| * <ul> |
| * <li>{@link #writeExternal(ObjectOutput)}</li> |
| * <li>{@link #readExternal(ObjectInput)}</li> |
| * <li>{@link #writeString(ObjectOutput, String)}</li> |
| * <li>{@link #readString(ObjectInput)}</li> |
| * <li>{@link #writeRanges(ObjectOutput, HashMap)}</li> |
| * <li>{@link #readRanges(ObjectInput)}</li> |
| * </ul> |
| * |
| * This is because if any of these change then previously externalized {@link JSPTranslator}s |
| * will no longer be able to be read by the new implementation. This value is used by |
| * the {@link Externalizable} API automatically to determine if the file being read is of the |
| * correct version to be read by the current implementation of the {@link JSPTranslator}</p> |
| * |
| * @see #writeExternal(ObjectOutput) |
| * @see #readExternal(ObjectInput) |
| * @see #writeString(ObjectOutput, String) |
| * @see #readString(ObjectInput) |
| * @see #writeRanges(ObjectOutput, HashMap) |
| * @see #readRanges(ObjectInput) |
| */ |
| private static final long serialVersionUID = 6L; |
| |
| /** for debugging */ |
| private static final boolean DEBUG = Boolean.valueOf(Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jspjavamapping")).booleanValue(); //$NON-NLS-1$ |
| |
| /** handy plugin ID constant */ |
| private static final String JSP_CORE_PLUGIN_ID = "org.eclipse.jst.jsp.core"; //$NON-NLS-1$ |
| |
| // constants for reading extension point |
| /** Default EL Translator extension ID */ |
| private static final String DEFAULT_JSP_EL_TRANSLATOR_ID = "org.eclipse.jst.jsp.defaultJSP20"; //$NON-NLS-1$ |
| |
| /** the name of the element in the extension point */ |
| private static final String EL_TRANSLATOR_EXTENSION_NAME = "elTranslator"; //$NON-NLS-1$ |
| |
| /** the name of the property in the extension point */ |
| private static final String ELTRANSLATOR_PROP_NAME = "ELTranslator"; //$NON-NLS-1$ |
| |
| |
| // these constants are commonly used strings during translation |
| /** end line characters */ |
| public static final String ENDL = "\n"; //$NON-NLS-1$ |
| |
| /** footer text */ |
| private static final String FOOTER = "}}"; //$NON-NLS-1$ |
| |
| /** exception declaration */ |
| private static final String EXCEPTION = "Throwable exception = new Throwable();"; //$NON-NLS-1$ |
| |
| /** expression prefix */ |
| public static final String EXPRESSION_PREFIX = "out.print("; //$NON-NLS-1$ |
| |
| /** expression suffix */ |
| public static final String EXPRESSION_SUFFIX = ");"; //$NON-NLS-1$ |
| |
| /** try/catch start */ |
| private static final String TRY_CATCH_START = ENDL + "try {" + ENDL; //$NON-NLS-1$ |
| |
| /** try/catch end */ |
| private static final String TRY_CATCH_END = " } catch (java.lang.Exception e) {} " + ENDL; //$NON-NLS-1$ |
| |
| /** JSP tag name prefix */ |
| static final String JSP_PREFIX = "jsp:"; //$NON-NLS-1$ |
| |
| // these constants are to keep track of what type of code is currently being translated |
| /** code in question is standard JSP */ |
| protected final static int STANDARD_JSP = 0; |
| |
| /** code in question is embedded (JSP as an attribute or within comment tags) */ |
| protected final static int EMBEDDED_JSP = 1; |
| |
| /** code in question is a JSP declaration */ |
| protected final static int DECLARATION = 2; |
| |
| /** code in question is a JSP expression */ |
| protected final static int EXPRESSION = 4; |
| |
| /** code in question is a JSP scriptlet */ |
| protected final static int SCRIPTLET = 8; |
| |
| |
| // strings specific to this translation |
| /** translated class header */ |
| String fClassHeader = null; |
| |
| /** translated class name */ |
| String fClassname = null; |
| |
| /** translated class super class */ |
| String fSuperclass = null; |
| |
| /** translated class imports */ |
| String fImplicitImports = null; |
| |
| /** translated class service header */ |
| String fServiceHeader = null; |
| |
| /** descriptor for the API available to build against */ |
| ServletAPIDescriptor fServletAPIDescriptor = ServletAPIDescriptor.DEFAULT; |
| |
| /** The context of the translation */ |
| String fContext = null; |
| |
| /** The context's session */ |
| String fSession = null; |
| |
| /** translated user defined imports */ |
| private StringBuffer fUserImports = new StringBuffer(); |
| |
| //translation specific state |
| /** {@link IDOMModel} for the JSP file being translated */ |
| IDOMModel fStructuredModel = null; |
| |
| /** {@link IStructuredDocument} for the JSP file being translated */ |
| IStructuredDocument fStructuredDocument = null; |
| |
| /** the EL translator */ |
| private IJSPELTranslator fELTranslator = null; |
| |
| /** reported translation problems */ |
| private List fTranslationProblems = new ArrayList(); |
| |
| /** fSourcePosition = position in JSP source */ |
| private int fSourcePosition = -1; |
| |
| /** fRelativeOffest = offset in the buffer there the cursor is */ |
| private int fRelativeOffset = -1; |
| |
| /** fCursorPosition = offset in the translated java document */ |
| private int fCursorPosition = -1; |
| |
| /** some page directive attributes */ |
| private boolean fIsErrorPage = false; |
| private boolean fCursorInExpression = false; |
| private boolean fIsInASession = true; |
| |
| /** user java code in body of the service method */ |
| private StringBuffer fUserCode = new StringBuffer(); |
| /** user EL Expression */ |
| private StringBuffer fUserELExpressions = new StringBuffer(); |
| /** user defined vars declared in the beginning of the class */ |
| private StringBuffer fUserDeclarations = new StringBuffer(); |
| |
| /** |
| * A map of tag names to tag library variable information; used to store |
| * the ones needed for AT_END variable support. |
| */ |
| private StackMap fTagToVariableMap = null; |
| private Map fAtBeginVariableMap = null; |
| private Stack fAtBeginScopeStack = new Stack(); |
| private Stack fUseBeansStack = new Stack(); |
| |
| /** the final translated java document */ |
| private StringBuffer fResult; |
| |
| /** the buffer where the cursor is */ |
| private StringBuffer fCursorOwner = null; |
| |
| private IStructuredDocumentRegion fCurrentNode; |
| |
| /** flag for if the cursor is in the current regionb eing translated */ |
| private boolean fInCodeRegion = false; |
| |
| /** used to avoid infinite looping include files */ |
| private Stack fIncludes = null; |
| private Set fIncludedPaths = new HashSet(2); |
| private boolean fProcessIncludes = true; |
| /** mostly for helper classes, so they parse correctly */ |
| private ArrayList fBlockMarkers = null; |
| /** |
| * for keeping track of offset in user buffers while document is being |
| * built |
| */ |
| private int fOffsetInUserImports = 0; |
| private int fOffsetInUserDeclarations = 0; |
| private int fOffsetInUserCode = 0; |
| |
| /** correlates ranges (positions) in java to ranges in jsp */ |
| private Map<Position,Position> fJava2JspRanges = new HashMap<>(); |
| |
| /** |
| * map of ranges in fUserImports (relative to the start of the buffer) to |
| * ranges in source JSP buffer. |
| */ |
| private HashMap fImportRanges = new HashMap(); |
| /** |
| * map of ranges in fUserCode (relative to the start of the buffer) to |
| * ranges in source JSP buffer. |
| */ |
| private HashMap fCodeRanges = new HashMap(); |
| /** |
| * map of ranges in fUserDeclarations (relative to the start of the |
| * buffer) to ranges in source JSP buffer. |
| */ |
| private HashMap fDeclarationRanges = new HashMap(); |
| |
| private HashMap fUseBeanRanges = new HashMap(); |
| |
| private HashMap fUserELRanges = new HashMap(); |
| |
| /** |
| * ranges that don't directly map from java code to JSP code (eg. |
| * <%@include file="included.jsp"%> |
| */ |
| private HashMap fIndirectRanges = new HashMap(); |
| |
| private IProgressMonitor fProgressMonitor = null; |
| |
| /** |
| * save JSP document text for later use may just want to read this from |
| * the file or strucdtured document depending what is available |
| */ |
| private StringBuffer fJspTextBuffer = new StringBuffer(); |
| |
| /** EL Translator ID (pluggable) */ |
| private String fELTranslatorID; |
| |
| /** |
| * <code>true</code> if code has been found, such as HTML tags, that is not translated |
| * <code>false</code> otherwise. Useful for deciding if a place holder needs to be |
| * written to translation |
| */ |
| private boolean fFoundNonTranslatedCode; |
| |
| /** |
| * <code>true</code> if code has been translated for the current region, |
| * <code>false</code> otherwise |
| */ |
| private boolean fCodeTranslated; |
| |
| /** The model path as it was persisted */ |
| private IPath fSavedModelPath = null; |
| |
| /** |
| * A structure for holding a region collection marker and list of variable |
| * information. The region can be used later for positioning validation |
| * messages. |
| */ |
| static class RegionTags { |
| ITextRegionCollection region; |
| CustomTag tag; |
| Collection scopedVarNames = null; |
| |
| RegionTags(ITextRegionCollection region, CustomTag tag, Collection scopedVarNames) { |
| this.region = region; |
| this.tag = tag; |
| this.scopedVarNames = scopedVarNames; |
| } |
| } |
| |
| public JSPTranslator() { |
| init(); |
| } |
| |
| /** |
| * configure using an XMLNode |
| * |
| * @param node |
| * @param monitor |
| */ |
| private void configure(IDOMNode node, IProgressMonitor monitor) { |
| |
| fProgressMonitor = monitor; |
| fStructuredModel = node.getModel(); |
| String baseLocation = fStructuredModel.getBaseLocation(); |
| |
| fELTranslatorID = getELTranslatorProperty(baseLocation); |
| |
| fStructuredDocument = fStructuredModel.getStructuredDocument(); |
| |
| fServletAPIDescriptor = DeploymentDescriptorPropertyCache.getInstance().getServletAPIVersion(ResourcesPlugin.getWorkspace().getRoot().getProject(new Path(baseLocation).segment(0))); |
| fSuperclass = fServletAPIDescriptor.getRootPackage() + ".http.HttpServlet"; //$NON-NLS-1$ |
| fIsDefaultSuperclass = true; |
| |
| init(); |
| |
| String className = createClassname(node); |
| if (className.length() > 0) { |
| setClassname(className); |
| fClassHeader = "public class " + className + " extends "; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| } |
| |
| /** |
| * memory saving configure (no StructuredDocument in memory) currently |
| * doesn't handle included files |
| * |
| * @param jspFile |
| * @param monitor |
| */ |
| private void configure(IFile jspFile, IProgressMonitor monitor) { |
| // when configured on a file |
| // fStructuredModel, fPositionNode, fModelQuery, fStructuredDocument |
| // are all null |
| fProgressMonitor = monitor; |
| |
| fELTranslatorID = getELTranslatorProperty(jspFile); |
| |
| fServletAPIDescriptor = DeploymentDescriptorPropertyCache.getInstance().getServletAPIVersion(jspFile.getProject()); |
| fSuperclass = fServletAPIDescriptor.getRootPackage() + ".http.HttpServlet"; //$NON-NLS-1$ |
| fIsDefaultSuperclass = true; |
| |
| init(); |
| |
| String className = createClassname(jspFile); |
| if (className.length() > 0) { |
| setClassname(className); |
| fClassHeader = "public class " + className + " extends "; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| /** |
| * Set the jsp text from an IFile |
| * |
| * @param jspFile |
| */ |
| private void setJspText(IFile jspFile) { |
| try { |
| BufferedInputStream in = new BufferedInputStream(jspFile.getContents()); |
| BufferedReader reader = new BufferedReader(new InputStreamReader(in)); |
| String line = null; |
| while ((line = reader.readLine()) != null) { |
| fJspTextBuffer.append(line); |
| fJspTextBuffer.append(ENDL); |
| } |
| reader.close(); |
| } |
| catch (CoreException e) { |
| Logger.logException(e); |
| } |
| catch (IOException e) { |
| Logger.logException(e); |
| } |
| } |
| |
| /** |
| * Get the value of the ELTranslator property from a workspace relative |
| * path string |
| * |
| * @param baseLocation |
| * Workspace-relative string path |
| * @return Value of the ELTranslator property associated with the project. |
| */ |
| private String getELTranslatorProperty(String baseLocation) { |
| IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); |
| String elTranslatorValue = null; |
| IFile file = workspaceRoot.getFile(new Path(baseLocation)); |
| if (file != null) { |
| elTranslatorValue = getELTranslatorProperty(file); |
| } |
| return elTranslatorValue; |
| } |
| |
| /** |
| * Get the value of the ELTranslator property from an IFile |
| * |
| * @param file |
| * IFile |
| * @return Value of the ELTranslator property associated with the project. |
| */ |
| private String getELTranslatorProperty(IFile file) { |
| String elTranslatorValue = null; |
| if (file != null) { |
| if (file.exists()) { |
| try { |
| elTranslatorValue = file.getPersistentProperty(new QualifiedName(JSP_CORE_PLUGIN_ID, ELTRANSLATOR_PROP_NAME)); |
| if (null == elTranslatorValue) { |
| |
| elTranslatorValue = file.getProject().getPersistentProperty(new QualifiedName(JSP_CORE_PLUGIN_ID, ELTRANSLATOR_PROP_NAME)); |
| } |
| } |
| catch (CoreException e) { |
| // ISSUE: why do we log this here? Instead of allowing to |
| // throwup? |
| Logger.logException(e); |
| } |
| |
| } |
| } |
| return elTranslatorValue; |
| } |
| |
| /** |
| * @param node |
| * @return the simple class name, not fully qualified |
| */ |
| private String createClassname(IDOMNode node) { |
| String classname = ""; //$NON-NLS-1$ |
| if (node != null) { |
| String base = node.getModel().getBaseLocation(); |
| classname = JSP2ServletNameUtil.mangle(base); |
| } |
| return classname; |
| } |
| |
| /** |
| * @param jspFile |
| * @return |
| */ |
| private String createClassname(IFile jspFile) { |
| |
| String classname = ""; //$NON-NLS-1$ |
| if (jspFile != null) { |
| classname = JSP2ServletNameUtil.mangle(jspFile.getFullPath().toString()); |
| } |
| return classname; |
| } |
| |
| private IJSPProblem createJSPProblem(final int problemEID, final int problemID, final String message, final int start, final int end) { |
| final char[] classname = fClassname.toCharArray(); |
| |
| /* |
| * Note: these problems would result in translation errors on the |
| * server, so the severity is not meant to be controllable |
| */ |
| IJSPProblem problem = new IJSPProblem() { |
| private int lineNumber = -1; |
| |
| public void setSourceStart(int sourceStart) { |
| } |
| |
| public void setSourceLineNumber(int lineNumber) { |
| this.lineNumber = lineNumber; |
| } |
| |
| public void setSourceEnd(int sourceEnd) { |
| } |
| |
| public boolean isInfo() { |
| return false; |
| } |
| |
| public boolean isWarning() { |
| return false; |
| } |
| |
| public boolean isError() { |
| return true; |
| } |
| |
| public int getSourceStart() { |
| return start; |
| } |
| |
| public int getSourceLineNumber() { |
| return lineNumber; |
| } |
| |
| public int getSourceEnd() { |
| return end; |
| } |
| |
| public char[] getOriginatingFileName() { |
| return classname; |
| } |
| |
| public String getMessage() { |
| return message; |
| } |
| |
| public int getID() { |
| return problemID; |
| } |
| |
| public String[] getArguments() { |
| return new String[0]; |
| } |
| |
| public int getEID() { |
| return problemEID; |
| } |
| }; |
| |
| // Update Source Line Number if a document is initialized |
| // (Because in this case postReadExternalSetup() method may not be called) |
| if (fStructuredDocument != null) { |
| problem.setSourceLineNumber(fStructuredDocument.getLineOfOffset(start)); |
| } |
| |
| return problem; |
| } |
| |
| public void setClassname(String classname) { |
| this.fClassname = classname; |
| } |
| |
| public String getClassname() { |
| return this.fClassname != null ? this.fClassname : "GenericJspServlet"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * So that the JSPTranslator can be reused. |
| */ |
| public void reset(IDOMNode node, IProgressMonitor progress) { |
| |
| // initialize some things on node |
| configure(node, progress); |
| reset(); |
| // set the jsp text buffer |
| fJspTextBuffer.append(fStructuredDocument.get()); |
| } |
| |
| /** |
| * conservative version (no StructuredDocument/Model) |
| * |
| * @param jspFile |
| * @param progress |
| */ |
| public void reset(IFile jspFile, IProgressMonitor progress) { |
| |
| // initialize some things on node |
| configure(jspFile, progress); |
| reset(); |
| // set the jsp text buffer |
| setJspText(jspFile); |
| } |
| |
| /** |
| * Reinitialize some fields |
| */ |
| void reset() { |
| |
| // reset progress monitor |
| if (fProgressMonitor != null) |
| fProgressMonitor.setCanceled(false); |
| |
| // reinit fields |
| fSourcePosition = -1; |
| fRelativeOffset = -1; |
| fCursorPosition = -1; |
| |
| fIsErrorPage = fCursorInExpression = false; |
| fIsInASession = true; |
| |
| fUserCode = new StringBuffer(); |
| fUserDeclarations = new StringBuffer(); |
| fUserImports = new StringBuffer(); |
| fUserELExpressions = new StringBuffer(); |
| |
| fResult = null; |
| fCursorOwner = null; // the buffer where the cursor is |
| |
| fCurrentNode = null; |
| fInCodeRegion = false; // flag for if cursor is in the current region |
| // being translated |
| |
| if (fIncludes != null) |
| fIncludes.clear(); |
| |
| fBlockMarkers = null; |
| |
| fOffsetInUserImports = 0; |
| fOffsetInUserDeclarations = 0; |
| fOffsetInUserCode = 0; |
| |
| fJava2JspRanges.clear(); |
| fImportRanges.clear(); |
| fCodeRanges.clear(); |
| fUseBeanRanges.clear(); |
| fDeclarationRanges.clear(); |
| fUserELRanges.clear(); |
| fIndirectRanges.clear(); |
| fIncludedPaths.clear(); |
| |
| fJspTextBuffer = new StringBuffer(); |
| |
| fFoundNonTranslatedCode = false; |
| fCodeTranslated = false; |
| } |
| |
| /** |
| * @return just the "shell" of a servlet, nothing contributed from the JSP |
| * doc |
| */ |
| public final StringBuffer getEmptyTranslation() { |
| reset(); |
| buildResult(true); |
| return getTranslation(); |
| } |
| |
| /** |
| * <p>put the final java document together</p> |
| * |
| * @param updateRanges <code>true</code> if the ranges need to be updated as the result |
| * is built, <code>false</code> if the ranges have already been updated. This is useful |
| * if building a result from a persisted {@link JSPTranslator}. |
| */ |
| private final void buildResult(boolean updateRanges) { |
| // to build the java document this is the order: |
| // |
| // + default imports |
| // + user imports |
| // + class header |
| // [+ error page] |
| // + user declarations |
| // + service method header |
| // + try/catch start |
| // + user code |
| // + try/catch end |
| // + service method footer |
| fResult = new StringBuffer(fImplicitImports.length() + fUserImports.length() + fClassHeader.length() + |
| fUserDeclarations.length() + fServiceHeader.length() + TRY_CATCH_START.length() |
| + fUserCode.length() + TRY_CATCH_END.length() + FOOTER.length()); |
| |
| int javaOffset = 0; |
| |
| fResult.append(fImplicitImports); |
| javaOffset += fImplicitImports.length(); |
| |
| // updateRanges(fIndirectImports, javaOffset); |
| if(updateRanges) { |
| updateRanges(fImportRanges, javaOffset); |
| } |
| // user imports |
| append(fUserImports); |
| javaOffset += fUserImports.length(); |
| |
| // class header |
| fResult.append(fClassHeader); |
| javaOffset += fClassHeader.length(); |
| fResult.append(fSuperclass + "{" + ENDL); //$NON-NLS-1$ |
| javaOffset += fSuperclass.length() + 2; |
| |
| List errorTypeNames = new ArrayList(2); |
| String decodedSuperType = decodeType(fSuperclass); |
| if (!isTypeFound(decodedSuperType, errorTypeNames)) { |
| for (int i = 0; i < errorTypeNames.size(); i++) { |
| Object problem = createJSPProblem(IJSPProblem.F_PROBLEM_ID_LITERAL, IProblem.UndefinedType, MessageFormat.format(JSPCoreMessages.JSPDirectiveValidator_8, (Object[]) new String[]{errorTypeNames.get(i).toString()}), 0, 1); |
| // if its the default supertype, and it was based on the facet, mention that |
| if (fIsDefaultSuperclass && fServletAPIDescriptor.getOrigin().equals(ServletAPIDescriptor.ORIGIN.FACET)) { |
| problem = createJSPProblem(IJSPProblem.F_PROBLEM_ID_LITERAL, IProblem.UndefinedType, MessageFormat.format(JSPCoreMessages.JSPDirectiveValidator_13, errorTypeNames.get(i).toString(), String.valueOf(fServletAPIDescriptor.getAPIversion())), 0, 1); |
| } |
| else if (fIsDefaultSuperclass && fServletAPIDescriptor.getOrigin().equals(ServletAPIDescriptor.ORIGIN.FFACET)) { |
| problem = createJSPProblem(IJSPProblem.F_PROBLEM_ID_LITERAL, IProblem.UndefinedType, MessageFormat.format(JSPCoreMessages.JSPDirectiveValidator_14, errorTypeNames.get(i).toString(), String.valueOf(fServletAPIDescriptor.getAPIversion())), 0, 1); |
| } |
| fTranslationProblems.add(problem); |
| } |
| } |
| |
| if(updateRanges) { |
| updateRanges(fDeclarationRanges, javaOffset); |
| } |
| // user declarations |
| append(fUserDeclarations); |
| javaOffset += fUserDeclarations.length(); |
| |
| if(updateRanges) { |
| updateRanges(fUserELRanges, javaOffset); |
| } |
| append(fUserELExpressions); |
| javaOffset += fUserELExpressions.length(); |
| |
| fResult.append(fServiceHeader); |
| javaOffset += fServiceHeader.length(); |
| // session participant |
| if (fIsInASession) { |
| final String sessionVariableDeclaration = fServletAPIDescriptor.getRootPackage() + ".http.HttpSession session = "+ fSession + ENDL; //$NON-NLS-1$ |
| fResult.append(sessionVariableDeclaration); |
| javaOffset += sessionVariableDeclaration.length(); |
| } |
| // error page |
| if (fIsErrorPage) { |
| fResult.append(EXCEPTION); |
| javaOffset += EXCEPTION.length(); |
| } |
| |
| |
| fResult.append(TRY_CATCH_START); |
| javaOffset += TRY_CATCH_START.length(); |
| |
| if(updateRanges) { |
| updateRanges(fCodeRanges, javaOffset); |
| } |
| |
| // user code |
| append(fUserCode); |
| javaOffset += fUserCode.length(); |
| |
| |
| fResult.append(TRY_CATCH_END); |
| javaOffset += TRY_CATCH_END.length(); |
| |
| // footer |
| fResult.append(FOOTER); |
| javaOffset += FOOTER.length(); |
| |
| fJava2JspRanges.putAll(fImportRanges); |
| fJava2JspRanges.putAll(fDeclarationRanges); |
| fJava2JspRanges.putAll(fCodeRanges); |
| fJava2JspRanges.putAll(fUserELRanges); |
| |
| } |
| |
| /** |
| * @param javaRanges |
| * @param offsetInJava |
| */ |
| private void updateRanges(HashMap rangeMap, int offsetInJava) { |
| // just need to update java ranges w/ the offset we now know |
| Iterator it = rangeMap.keySet().iterator(); |
| while (it.hasNext()) |
| ((Position) it.next()).offset += offsetInJava; |
| } |
| |
| /** |
| * map of ranges (positions) in java document to ranges in jsp document |
| * |
| * @return a map of java positions to jsp positions. |
| */ |
| public Map<Position,Position> getJava2JspRanges() { |
| return fJava2JspRanges; |
| } |
| |
| /** |
| * map of ranges in jsp document to ranges in java document. |
| * |
| * @return a map of jsp positions to java positions, or null if no |
| * translation has occured yet (the map hasn't been built). |
| */ |
| public Map<Position,Position> getJsp2JavaRanges() { |
| if (fJava2JspRanges == null) |
| return null; |
| Map<Position,Position> flipFlopped = new HashMap<>(); |
| Iterator<Position> keys = fJava2JspRanges.keySet().iterator(); |
| Position range = null; |
| while (keys.hasNext()) { |
| range = keys.next(); |
| flipFlopped.put(fJava2JspRanges.get(range), range); |
| } |
| return flipFlopped; |
| } |
| |
| public HashMap getJava2JspImportRanges() { |
| return fImportRanges; |
| } |
| |
| public HashMap getJava2JspUseBeanRanges() { |
| return fUseBeanRanges; |
| } |
| |
| public HashMap getJava2JspIndirectRanges() { |
| return fIndirectRanges; |
| } |
| |
| /** |
| * Adds to the jsp<->java map by default |
| * |
| * @param value |
| * a comma delimited list of imports |
| */ |
| protected void addImports(String value) { |
| addImports(value, true); |
| } |
| |
| /** |
| * Pass in a comma delimited list of import values, appends each to the |
| * final result buffer |
| * |
| * @param value |
| * a comma delimited list of imports |
| */ |
| protected void addImports(String value, boolean addToMap) { |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=81687 |
| // added the "addToMap" parameter to exclude imports originating |
| // from included JSP files to be added to the jsp<->java mapping |
| StringTokenizer st = new StringTokenizer(value, ",", false); //$NON-NLS-1$ |
| String tok = ""; //$NON-NLS-1$ |
| // String appendage = ""; //$NON-NLS-1$ |
| while (st.hasMoreTokens()) { |
| tok = st.nextToken(); |
| appendImportToBuffer(tok, fCurrentNode, addToMap); |
| } |
| } |
| |
| /** |
| * appends the given stringbuffer to the final result buffer, keeping |
| * track of the position of its contents |
| */ |
| protected void append(StringBuffer buf) { |
| if (getCursorOwner() == buf) { |
| fCursorPosition = fResult.length() + getRelativeOffset(); |
| } |
| fResult.append(buf.toString()); |
| } |
| |
| /** |
| * Only valid after a configure(...), translate(...) or |
| * translateFromFile(...) call |
| * |
| * @return the current result (java translation) buffer |
| */ |
| public final StringBuffer getTranslation() { |
| |
| if (DEBUG) { |
| StringBuffer debugString = new StringBuffer(); |
| try { |
| Iterator it = fJava2JspRanges.keySet().iterator(); |
| while (it.hasNext()) { |
| debugString.append("--------------------------------------------------------------\n"); //$NON-NLS-1$ |
| Position java = (Position) it.next(); |
| debugString.append("Java range:[" + java.offset + ":" + java.length + "]\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| debugString.append("[" + fResult.toString().substring(java.offset, java.offset + java.length) + "]\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| debugString.append("--------------------------------------------------------------\n"); //$NON-NLS-1$ |
| debugString.append("|maps to...|\n"); //$NON-NLS-1$ |
| debugString.append("==============================================================\n"); //$NON-NLS-1$ |
| Position jsp = fJava2JspRanges.get(java); |
| debugString.append("JSP range:[" + jsp.offset + ":" + jsp.length + "]\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| debugString.append("[" + fJspTextBuffer.toString().substring(jsp.offset, jsp.offset + jsp.length) + "]\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| debugString.append("==============================================================\n"); //$NON-NLS-1$ |
| debugString.append("\n"); //$NON-NLS-1$ |
| debugString.append("\n"); //$NON-NLS-1$ |
| } |
| } |
| catch (Exception e) { |
| Logger.logException("JSPTranslation error", e); //$NON-NLS-1$ |
| } |
| Logger.log(Logger.INFO_DEBUG, debugString.toString()); |
| } |
| |
| return fResult; |
| } |
| |
| public List getTranslationProblems() { |
| return fTranslationProblems; |
| } |
| |
| /** |
| * Only valid after a configure(...), translate(...) or |
| * translateFromFile(...) call |
| * |
| * @return the text in the JSP file |
| */ |
| public final String getJspText() { |
| return fJspTextBuffer.toString(); |
| } |
| |
| |
| protected void addTaglibVariables(String tagToAdd, ITextRegionCollection customTag) { |
| addTaglibVariables(tagToAdd, customTag, -1); |
| } |
| /** |
| * Add the server-side scripting variables used by this tag, along with |
| * any scoping. |
| * |
| * @param tagToAdd |
| * @param customTag |
| */ |
| protected void addTaglibVariables(String tagToAdd, ITextRegionCollection customTag, int index) { |
| if (customTag.getFirstRegion().getType().equals(DOMRegionContext.XML_TAG_OPEN)) { |
| /* |
| * Start tag |
| */ |
| addStartTagVariable(tagToAdd, customTag,index); |
| } |
| else if (customTag.getFirstRegion().getType().equals(DOMRegionContext.XML_END_TAG_OPEN)) { |
| /* |
| * End tag |
| */ |
| addEndTagVariable(tagToAdd, customTag); |
| } |
| } |
| |
| private void addEndTagVariable(String tagToAdd, ITextRegionCollection customTag){ |
| IFile f = getFile(); |
| if (f == null || !f.exists()) |
| return; |
| String decl = ""; //$NON-NLS-1$ |
| RegionTags regionTag = (RegionTags) fTagToVariableMap.pop(tagToAdd); |
| if (regionTag != null) { |
| // even an empty array will indicate a need for a closing brace |
| TaglibVariable[] taglibVars = regionTag.tag.getTagVariables(); |
| StringBuffer text = new StringBuffer(); |
| if (regionTag.tag.isIterationTag()) |
| doAfterBody(text, regionTag); |
| text.append("} // </"); //$NON-NLS-1$ |
| text.append(tagToAdd); |
| text.append(">\n"); //$NON-NLS-1$ |
| appendToBuffer(text.toString(), fUserCode, false, customTag); |
| for (int i = 0; i < taglibVars.length; i++) { |
| if (taglibVars[i].getScope() == VariableInfo.AT_END) { |
| decl = taglibVars[i].getDeclarationString(fContext); |
| appendToBuffer(decl, fUserCode, true, customTag); |
| } |
| } |
| |
| fAtBeginVariableMap.remove( fAtBeginScopeStack.pop() ); |
| } |
| else { |
| /* |
| * Since something should have been in the map because of a |
| * start tag, its absence now means an unbalanced end tag. |
| * Extras will be checked later to flag unbalanced start tags. |
| */ |
| IJSPProblem missingStartTag = createJSPProblem(IJSPProblem.StartCustomTagMissing, IJSPProblem.F_PROBLEM_ID_LITERAL, NLS.bind(JSPCoreMessages.JSPTranslator_4, tagToAdd), customTag.getStartOffset(), customTag.getEndOffset()); |
| fTranslationProblems.add(missingStartTag); |
| } |
| } |
| private void addStartTagVariable(String tagToAdd, ITextRegionCollection customTag, int index){ |
| IFile f = getFile(); |
| |
| if (f == null || !f.exists()) |
| return; |
| TaglibHelper helper = TaglibHelperManager.getInstance().getTaglibHelper(f); |
| String decl = ""; //$NON-NLS-1$ |
| List problems = new ArrayList(); |
| CustomTag tag = helper.getCustomTag(tagToAdd, getStructuredDocument(), customTag, problems); |
| TaglibVariable[] taglibVars = tag.getTagVariables(); |
| fTranslationProblems.addAll(problems); |
| Set scopedVarNames = new HashSet(0); |
| /* |
| * Add AT_BEGIN variables |
| */ |
| for (int i = 0; i < taglibVars.length; i++) { |
| if (taglibVars[i].getScope() == VariableInfo.AT_BEGIN) { |
| scopedVarNames.add(taglibVars[i].getVarName()); |
| boolean declaredInParentScope = false; |
| /* |
| * Check to see if we have already declared this variable |
| * once, if so then just reassign it instead. Declaring twice |
| * in the same scope should cause an error, so we're only |
| * checking parent scopes and the current scope. |
| */ |
| RegionTags[] parentTags = (RegionTags[]) fTagToVariableMap.values().toArray(new RegionTags[fTagToVariableMap.size()]); |
| String varName = taglibVars[i].getVarName(); |
| for (int j = 0; j < parentTags.length && !declaredInParentScope; j++) { |
| declaredInParentScope |= parentTags[j].scopedVarNames.contains(varName); |
| } |
| |
| Set currentAtBeginVars = (Set) fAtBeginVariableMap.get( fAtBeginScopeStack.peek() ); |
| |
| boolean declaredInCurrentScope = currentAtBeginVars != null && currentAtBeginVars.contains( varName ); |
| |
| if (declaredInParentScope || declaredInCurrentScope) { |
| decl = taglibVars[i].getDeclarationString(false, fContext, TaglibVariable.M_REASSIGN); |
| } |
| else { |
| decl = taglibVars[i].getDeclarationString(fContext); |
| |
| if( currentAtBeginVars == null ) { |
| currentAtBeginVars = new HashSet(); |
| currentAtBeginVars.add( varName ); |
| fAtBeginVariableMap.put( fAtBeginScopeStack.peek(), currentAtBeginVars ); |
| } |
| else { |
| currentAtBeginVars.add( varName ); |
| } |
| } |
| |
| appendToBuffer(decl, fUserCode, true, customTag); |
| } |
| } |
| boolean isEmptyTag = false; |
| if (index != -1) |
| isEmptyTag= isEmptyTag(customTag, index); |
| else |
| isEmptyTag= isEmptyTag(customTag); |
| |
| /* |
| * Add a single { to limit the scope of NESTED variables |
| */ |
| StringBuffer text = new StringBuffer(); |
| if (!isEmptyTag && tag.isIterationTag() && tag.getTagClassName() != null) { |
| text.append("\nwhile(true) "); //$NON-NLS-1$ |
| } |
| text.append("{ // <"); //$NON-NLS-1$ |
| text.append(tagToAdd); |
| if (isEmptyTag) |
| text.append("/>\n"); //$NON-NLS-1$ |
| else |
| text.append(">\n"); //$NON-NLS-1$ |
| |
| appendToBuffer(text.toString(), fUserCode, false, customTag); |
| |
| for (int i = 0; i < taglibVars.length; i++) { |
| if (taglibVars[i].getScope() == VariableInfo.NESTED) { |
| scopedVarNames.add(taglibVars[i].getVarName()); |
| decl = taglibVars[i].getDeclarationString(fContext); |
| appendToBuffer(decl, fUserCode, true, customTag); |
| } |
| } |
| /* |
| * For empty tags, add the corresponding } and AT_END variables immediately. |
| */ |
| if (isEmptyTag) { |
| text = new StringBuffer(); |
| text.append("} // <"); //$NON-NLS-1$ |
| text.append(tagToAdd); |
| text.append("/>\n"); //$NON-NLS-1$ |
| appendToBuffer(text.toString(), fUserCode, false, customTag); |
| /* Treat this as the end for empty tags */ |
| for (int i = 0; i < taglibVars.length; i++) { |
| if (taglibVars[i].getScope() == VariableInfo.AT_END) { |
| decl = taglibVars[i].getDeclarationString(fContext); |
| appendToBuffer(decl, fUserCode, false, customTag); |
| } |
| } |
| } |
| else { |
| /* |
| * For non-empty tags, remember the variable information |
| */ |
| fTagToVariableMap.push(tagToAdd, new RegionTags(customTag, tag, scopedVarNames)); |
| fAtBeginScopeStack.push( tagToAdd ); |
| } |
| |
| } |
| |
| private boolean isEmptyTag(ITextRegionCollection customTag, int index) { |
| String type = null; |
| // custom tag is embedded |
| ITextRegionList regions = customTag.getRegions(); |
| ITextRegion nextRegion = regions.get(index); |
| int size = customTag.getNumberOfRegions() ; |
| type = nextRegion.getType(); |
| while (index <= size && !(DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(type) || DOMRegionContext.XML_TAG_NAME.equals(type) || DOMRegionContext.XML_TAG_CLOSE.equals(type) )) { |
| nextRegion = regions.get(++index); |
| type = nextRegion.getType(); |
| } |
| |
| return DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(type); |
| } |
| |
| private boolean isEmptyTag(ITextRegionCollection customTag) { |
| ITextRegion lastRegion = customTag.getLastRegion(); |
| // custom tag is embedded |
| if (customTag instanceof ITextRegionContainer) { |
| ITextRegionList regions = customTag.getRegions(); |
| int size = customTag.getNumberOfRegions() - 1; |
| while (size > 0 && !(DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(lastRegion.getType()) || DOMRegionContext.XML_TAG_NAME.equals(lastRegion.getType()) || DOMRegionContext.XML_TAG_CLOSE.equals(lastRegion.getType()) )) { |
| lastRegion = regions.get(--size); |
| } |
| } |
| |
| return DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(lastRegion.getType()); |
| } |
| |
| private void addCustomTaglibVariables(String tagToAdd, ITextRegionCollection customTag, ITextRegion prevRegion, int index) { |
| //Can't judge by first region as start and end tag are part of same ContextRegionContainer |
| if (prevRegion != null && prevRegion.getType().equals(DOMRegionContext.XML_END_TAG_OPEN)) { |
| /* |
| * End tag |
| */ |
| addEndTagVariable(tagToAdd, customTag); |
| } |
| else if (prevRegion != null && prevRegion.getType().equals(DOMRegionContext.XML_TAG_OPEN)) { |
| /* |
| * Start tag |
| */ |
| addStartTagVariable(tagToAdd,customTag, index); |
| } |
| } |
| |
| private void doAfterBody(StringBuffer buffer, RegionTags regionTag) { |
| buffer.append("\tif ( (new "); //$NON-NLS-1$ |
| buffer.append(regionTag.tag.getTagClassName()); |
| buffer.append("()).doAfterBody() != " + fServletAPIDescriptor.getRootPackage() + ".jsp.tagext.BodyTag.EVAL_BODY_AGAIN)\n\t\tbreak;\n"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @return the workspace file for this model, null otherwise |
| */ |
| private IFile getFile() { |
| IFile f = null; |
| ITextFileBuffer buffer = FileBufferModelManager.getInstance().getBuffer(getStructuredDocument()); |
| if (buffer != null) { |
| IPath path = buffer.getLocation(); |
| if (path.segmentCount() > 1) { |
| f = ResourcesPlugin.getWorkspace().getRoot().getFile(path); |
| } |
| if (f != null && f.isAccessible()) { |
| return f; |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * used by inner helper class (XMLJSPRegionHelper, JSPIncludeRegionHelper) |
| */ |
| public List getBlockMarkers() { |
| if (fBlockMarkers == null) |
| fBlockMarkers = new ArrayList(); |
| return fBlockMarkers; |
| } |
| |
| /** |
| * the main control loop for translating the document, driven by the |
| * structuredDocument nodes |
| */ |
| public void translate() { |
| if (fTagToVariableMap == null) { |
| fTagToVariableMap = new StackMap(); |
| } |
| if( fAtBeginVariableMap == null ) { |
| fAtBeginVariableMap = new HashMap(); |
| } |
| fAtBeginScopeStack.clear(); |
| fAtBeginScopeStack.push( "__root__" ); // need to existing scope for top level customtags |
| fTranslationProblems.clear(); |
| |
| setCurrentNode(new ZeroStructuredDocumentRegion(fStructuredDocument, 0)); |
| translatePreludes(); |
| |
| setCurrentNode(fStructuredDocument.getFirstStructuredDocumentRegion()); |
| |
| while (getCurrentNode() != null && !isCanceled()) { |
| //no code has been translated for this region yet |
| fCodeTranslated = false; |
| // intercept HTML comment flat node |
| // also handles UNDEFINED (which is what CDATA comes in as) |
| // basically this part will handle any "embedded" JSP containers |
| if (getCurrentNode().getType() == DOMRegionContext.XML_COMMENT_TEXT || getCurrentNode().getType() == DOMRegionContext.XML_CDATA_TEXT || getCurrentNode().getType() == DOMRegionContext.UNDEFINED) { |
| translateXMLCommentNode(getCurrentNode()); |
| } |
| else { |
| // iterate through each region in the flat node |
| translateRegionContainer(getCurrentNode(), STANDARD_JSP); |
| } |
| |
| //if no code was translated for this region then found "non translated code" |
| if(!fCodeTranslated) { |
| fFoundNonTranslatedCode = true; |
| } |
| |
| if (getCurrentNode() != null) |
| advanceNextNode(); |
| } |
| |
| writePlaceHolderForNonTranslatedCode(); |
| |
| setCurrentNode(new ZeroStructuredDocumentRegion(fStructuredDocument, fStructuredDocument.getLength())); |
| translateCodas(); |
| |
| /* |
| * Any contents left in the map indicate start tags that never had end |
| * tags. While the '{' that is present without the matching '}' should |
| * cause a Java translation fault, that's not particularly helpful to |
| * a user who may only know how to use custom tags as tags. Ultimately |
| * unbalanced custom tags should just be reported as unbalanced tags, |
| * and unbalanced '{'/'}' only reported when the user actually |
| * unbalanced them with scriptlets. |
| */ |
| Iterator regionAndTaglibVariables = fTagToVariableMap.values().iterator(); |
| while (regionAndTaglibVariables.hasNext()) { |
| RegionTags regionTag = (RegionTags) regionAndTaglibVariables.next(); |
| ITextRegionCollection extraStartRegion = regionTag.region; |
| IJSPProblem missingEndTag = createJSPProblem(IJSPProblem.EndCustomTagMissing, IJSPProblem.F_PROBLEM_ID_LITERAL, NLS.bind(JSPCoreMessages.JSPTranslator_5,regionTag.tag.getTagName()), extraStartRegion.getStartOffset(), extraStartRegion.getEndOffset()); |
| fTranslationProblems.add(missingEndTag); |
| |
| StringBuffer text = new StringBuffer(); |
| // Account for iteration tags that have a missing end tag |
| if (regionTag.tag.isIterationTag()) |
| doAfterBody(text, regionTag); |
| text.append("} // [</"); //$NON-NLS-1$ |
| text.append(regionTag.tag.getTagName()); |
| text.append(">]"); //$NON-NLS-1$ |
| appendToBuffer(text.toString(), fUserCode, false, fStructuredDocument.getLastStructuredDocumentRegion()); |
| } |
| fTagToVariableMap.clear(); |
| fAtBeginVariableMap.clear(); |
| |
| /* |
| * Now do the same for jsp:useBean tags, whose contents get their own |
| * { & } |
| */ |
| while (!fUseBeansStack.isEmpty()) { |
| appendToBuffer("}", fUserCode, false, fStructuredDocument.getLastStructuredDocumentRegion()); //$NON-NLS-1$ |
| ITextRegionCollection extraStartRegion = (ITextRegionCollection) fUseBeansStack.pop(); |
| IJSPProblem missingEndTag = createJSPProblem(IJSPProblem.UseBeanEndTagMissing, IJSPProblem.F_PROBLEM_ID_LITERAL, NLS.bind(JSPCoreMessages.JSPTranslator_5,JSP11Namespace.ElementName.USEBEAN), extraStartRegion.getStartOffset(), extraStartRegion.getEndOffset()); |
| fTranslationProblems.add(missingEndTag); |
| } |
| |
| buildResult(true); |
| } |
| |
| /** |
| * Translates a region container (and XML JSP container, or <% JSP |
| * container). This method should only be called in this class and for |
| * containers in the primary structured document as all buffer appends |
| * will be direct. |
| */ |
| protected void setDocumentContent(IDocument document, InputStream contentStream, String charset) { |
| Reader in = null; |
| try { |
| in = new BufferedReader(new InputStreamReader(contentStream, charset), 2048); |
| StringBuffer buffer = new StringBuffer(2048); |
| char[] readBuffer = new char[2048]; |
| int n = in.read(readBuffer); |
| while (n > 0) { |
| buffer.append(readBuffer, 0, n); |
| n = in.read(readBuffer); |
| } |
| document.set(buffer.toString()); |
| } |
| catch (IOException x) { |
| // ignore |
| } |
| finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } |
| catch (IOException x) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| protected void init() { |
| String rootPackage = fServletAPIDescriptor.getRootPackage(); |
| |
| fClassname = "_JSPServlet"; //$NON-NLS-1$ |
| fClassHeader = "public class " + fClassname + " extends "; //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| fImplicitImports = "import " + rootPackage + ".*;" + ENDL + //$NON-NLS-1$ |
| "import "+ rootPackage + ".http.*;" + ENDL + //$NON-NLS-1$ |
| "import " + rootPackage + ".jsp.*;" + ENDL + ENDL; //$NON-NLS-1$ |
| |
| fServiceHeader = "public void _jspService(" + rootPackage + ".http.HttpServletRequest request," + //$NON-NLS-1$ |
| " " + rootPackage + ".http.HttpServletResponse response)" + ENDL + //$NON-NLS-1$ |
| "\t\tthrows java.io.IOException, " + rootPackage + ".ServletException {" + ENDL + //$NON-NLS-1$ |
| rootPackage + ".jsp.PageContext pageContext = JspFactory.getDefaultFactory().getPageContext(this, request, response, null, true, JspWriter.DEFAULT_BUFFER, true);" + ENDL + //$NON-NLS-1$ |
| rootPackage + ".ServletContext application = pageContext.getServletContext();" + ENDL + //$NON-NLS-1$ |
| rootPackage + ".ServletConfig config = pageContext.getServletConfig();" + ENDL + //$NON-NLS-1$ |
| rootPackage + ".jsp.JspWriter out = pageContext.getOut();" + ENDL + //$NON-NLS-1$ |
| "Object page = this;" + ENDL; //$NON-NLS-1$ |
| fSuperclass = rootPackage + ".http.HttpServlet"; //$NON-NLS-1$ |
| fIsDefaultSuperclass = true; |
| |
| fContext = "pageContext"; //$NON-NLS-1$ |
| fSession = fContext+".getSession();"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * |
| * @return the status of the translator's progress monitor, false if the |
| * monitor is null |
| */ |
| private boolean isCanceled() { |
| return (fProgressMonitor == null) ? false : fProgressMonitor.isCanceled(); |
| } |
| |
| private void advanceNextNode() { |
| setCurrentNode(getCurrentNode().getNext()); |
| if (getCurrentNode() != null) |
| setSourceReferencePoint(); |
| } |
| |
| private void setSourceReferencePoint() { |
| if (isJSP(getCurrentNode().getFirstRegion().getType())) { |
| Iterator it = getCurrentNode().getRegions().iterator(); |
| ITextRegion r = null; |
| while (it.hasNext()) { |
| r = (ITextRegion) it.next(); |
| if (r.getType() == DOMJSPRegionContexts.JSP_CONTENT || r.getType() == DOMRegionContext.XML_CONTENT) |
| break; |
| else if (r.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) |
| break; |
| else if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE && getCurrentNode().getFullText(r).trim().equals("import")) //$NON-NLS-1$ |
| break; |
| } |
| } |
| } |
| |
| /** |
| * translates a region container (and XML JSP container, or <% JSP |
| * container) |
| * |
| * This method should only be called in this class and for containers in |
| * the primary structured document as all buffer appends will be direct |
| */ |
| protected void translateRegionContainer(ITextRegionCollection container, int JSPType) { |
| |
| ITextRegionCollection containerRegion = container; |
| |
| Iterator regions = containerRegion.getRegions().iterator(); |
| ITextRegion region = null; |
| while (regions.hasNext()) { |
| region = (ITextRegion) regions.next(); |
| String type = region.getType(); |
| |
| // content assist was not showing up in JSP inside a javascript region |
| if (DOMRegionContext.BLOCK_TEXT == type) { |
| // check if it's nested jsp in a script tag... |
| if (region instanceof ITextRegionContainer) { |
| // pass in block text's container & iterator |
| Iterator regionIterator = ((ITextRegionCollection) region).getRegions().iterator(); |
| translateJSPNode(region, regionIterator, type, EMBEDDED_JSP); |
| } |
| else { |
| //be sure to combine all of the text from the block region |
| StringBuffer fullText = new StringBuffer(containerRegion.getFullText(region)); |
| while(regions.hasNext()) { |
| region = (ITextRegion)regions.next(); |
| // Do not immediately translate container regions, since they may use variables declared within the full text |
| if(region.getType() == DOMRegionContext.BLOCK_TEXT) { |
| fullText.append(containerRegion.getFullText(region)); |
| } else { |
| //update type for when we exit if statement for BLOCK_TEXT |
| type = region.getType(); |
| break; |
| } |
| } |
| |
| /** |
| * LIMITATION - Normally the script content within a |
| * script tag is a single document region with a single |
| * BLOCK_TEXT text region within it. Any JSP scripting |
| * will be within its own region container (for the sake |
| * of keeping the scripting open/content/end as a group) |
| * also of BLOCK_TEXT. That ignores custom tags that might |
| * be in there, though, as they require proper scoping and |
| * variable declaration to be performed even though |
| * they're not proper nodes in the DOM. The only way to |
| * really do this is to treat the entire script content as |
| * JSP content on its own, akin to an included segment. |
| * Further complicating this solution is that tagdependent |
| * custom tags have their comment marked as BLOCK_TEXT as |
| * well, so there's no clear way to tell the two cases |
| * apart. |
| */ |
| |
| // //////////////////////////////////////////////////////////////////////////////// |
| // THIS EMBEDDED JSP TEXT WILL COME OUT LATER WHEN |
| // PARTITIONING HAS |
| // SUPPORT FOR NESTED XML-JSP |
| // CMVC 241882 |
| decodeScriptBlock(fullText.toString(), containerRegion.getStartOffset()); |
| // //////////////////////////////////////////////////////////////////////////////// |
| } |
| } |
| // if (region instanceof ITextRegionCollection && |
| // ((ITextRegionCollection) region).getNumberOfRegions() > 0) { |
| // translateRegionContainer((ITextRegionCollection) region, |
| // EMBEDDED_JSP); |
| // } |
| if (type != null && isJSP(type)) // <%, <%=, <%!, <%@ |
| { |
| // translateJSPNode(region, regions, type, JSPType); |
| translateJSPNode(containerRegion, regions, type, JSPType); |
| } |
| else if (type != null && (type == DOMRegionContext.XML_TAG_OPEN || type == DOMRegionContext.XML_END_TAG_OPEN)) { |
| translateXMLNode(containerRegion, regions); |
| } |
| else if(type != null && type == DOMRegionContext.XML_CONTENT && region instanceof ITextRegionContainer) { |
| //this case was put in to parse EL that is not in an attribute |
| translateXMLContent((ITextRegionContainer)region); |
| } |
| //the end tags of these regions are "translated" in a sense |
| else if(type == DOMJSPRegionContexts.JSP_DIRECTIVE_CLOSE || |
| type == DOMJSPRegionContexts.JSP_CLOSE) { |
| this.fCodeTranslated = true; |
| } |
| } |
| } |
| |
| void decodeScriptBlock(String blockText, int startOfBlock) { |
| XMLJSPRegionHelper helper = new XMLJSPRegionHelper(this, false); |
| helper.addBlockMarker(new BlockMarker("jsp:scriptlet", null, DOMJSPRegionContexts.JSP_CONTENT, false)); //$NON-NLS-1$ |
| helper.addBlockMarker(new BlockMarker("jsp:expression", null, DOMJSPRegionContexts.JSP_CONTENT, false)); //$NON-NLS-1$ |
| helper.addBlockMarker(new BlockMarker("jsp:declaration", null, DOMJSPRegionContexts.JSP_CONTENT, false)); //$NON-NLS-1$ |
| helper.addBlockMarker(new BlockMarker("jsp:directive.include", null, DOMJSPRegionContexts.JSP_CONTENT, false)); //$NON-NLS-1$ |
| helper.addBlockMarker(new BlockMarker("jsp:directive.taglib", null, DOMJSPRegionContexts.JSP_CONTENT, false)); //$NON-NLS-1$ |
| helper.reset(blockText, startOfBlock); |
| // force parse |
| helper.forceParse(); |
| } |
| |
| /* |
| * returns string minus CDATA open and close text |
| */ |
| final public String stripCDATA(String text) { |
| String resultText = ""; //$NON-NLS-1$ |
| String CDATA_OPEN = "<![CDATA["; //$NON-NLS-1$ |
| String CDATA_CLOSE = "]]>"; //$NON-NLS-1$ |
| int start = 0; |
| int end = text.length(); |
| while (start < text.length()) { |
| if (text.indexOf(CDATA_OPEN, start) > -1) { |
| end = text.indexOf(CDATA_OPEN, start); |
| resultText += text.substring(start, end); |
| start = end + CDATA_OPEN.length(); |
| } |
| else if (text.indexOf(CDATA_CLOSE, start) > -1) { |
| end = text.indexOf(CDATA_CLOSE, start); |
| resultText += text.substring(start, end); |
| start = end + CDATA_CLOSE.length(); |
| } |
| else { |
| end = text.length(); |
| resultText += text.substring(start, end); |
| break; |
| } |
| } |
| return resultText; |
| } |
| |
| // END OF WORKAROUND CODE... |
| // ///////////////////////////////////////////////////////////////////////////////////// |
| /** |
| * determines if the type is a pure JSP type (not XML) |
| */ |
| protected boolean isJSP(String type) { |
| return ((type == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN || type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN || type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || type == DOMJSPRegionContexts.JSP_CONTENT || type == DOMJSPRegionContexts.JSP_EL_OPEN) && type != DOMRegionContext.XML_TAG_OPEN); |
| // checking XML_TAG_OPEN so <jsp:directive.xxx/> gets treated like |
| // other XML jsp tags |
| } |
| |
| /** |
| * This currently only detects EL content and translates it, |
| * but if other cases arise later then they could be added in here |
| * |
| * @param embeddedContainer the container that may contain EL |
| */ |
| protected void translateXMLContent(ITextRegionContainer embeddedContainer) { |
| ITextRegionList embeddedRegions = embeddedContainer.getRegions(); |
| int length = embeddedRegions.size(); |
| for (int i = 0; i < length; i++) { |
| ITextRegion delim = embeddedRegions.get(i); |
| String type = delim.getType(); |
| |
| // check next region to see if it's EL content |
| if (i + 1 < length) { |
| if((type == DOMJSPRegionContexts.JSP_EL_OPEN || type == DOMJSPRegionContexts.JSP_VBL_OPEN)) { |
| ITextRegion region = null; |
| |
| int start = delim.getEnd(); |
| while (++i < length) { |
| region = embeddedRegions.get(i); |
| if (region == null || !isELType(region.getType())) |
| break; |
| } |
| fLastJSPType = EXPRESSION; |
| String elText = embeddedContainer.getFullText().substring(start, (region != null ? region.getStart() : embeddedContainer.getLength() - 1)); |
| translateEL(elText, embeddedContainer.getText(delim), fCurrentNode, |
| embeddedContainer.getEndOffset(delim), elText.length()); |
| } |
| } |
| } |
| } |
| |
| private boolean isELType(String type) { |
| return DOMJSPRegionContexts.JSP_EL_CONTENT.equals(type) || DOMJSPRegionContexts.JSP_VBL_CONTENT.equals(type) || DOMJSPRegionContexts.JSP_EL_DQUOTE.equals(type) || DOMJSPRegionContexts.JSP_VBL_DQUOTE.equals(type) || DOMJSPRegionContexts.JSP_EL_QUOTED_CONTENT.equals(type) || DOMJSPRegionContexts.JSP_VBL_QUOTED_CONTENT.equals(type) || DOMJSPRegionContexts.JSP_EL_SQUOTE.equals(type) || DOMJSPRegionContexts.JSP_VBL_SQUOTE.equals(type); |
| } |
| |
| /** |
| * translates the various XMLJSP type nodes |
| * |
| * @param regions |
| * the regions of the XMLNode |
| */ |
| protected void translateXMLNode(ITextRegionCollection container, Iterator regions) { |
| // contents must be valid XHTML, translate escaped CDATA into what it |
| // really is... |
| ITextRegion r = null; |
| if (regions.hasNext()) { |
| r = (ITextRegion) regions.next(); |
| // <jsp:directive.xxx > comes in as this |
| if (r.getType() == DOMRegionContext.XML_TAG_NAME || r.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) |
| |
| { |
| String fullTagName = container.getText(r); |
| if (fullTagName.indexOf(':') > -1 && !fullTagName.startsWith(JSP_PREFIX)) { |
| addTaglibVariables(fullTagName, container,-1); // it |
| // may |
| // be a |
| // custom |
| // tag |
| } |
| StringTokenizer st = new StringTokenizer(fullTagName, ":.", false); //$NON-NLS-1$ |
| if (st.hasMoreTokens() && st.nextToken().equals("jsp")) //$NON-NLS-1$ |
| { |
| if (st.hasMoreTokens()) { |
| String jspTagName = st.nextToken(); |
| |
| if (jspTagName.equals("scriptlet")) //$NON-NLS-1$ |
| { |
| translateXMLJSPContent(SCRIPTLET); |
| } |
| else if (jspTagName.equals("expression")) //$NON-NLS-1$ |
| { |
| translateXMLJSPContent(EXPRESSION); |
| } |
| else if (jspTagName.equals("declaration")) //$NON-NLS-1$ |
| { |
| translateXMLJSPContent(DECLARATION); |
| } |
| else if (jspTagName.equals("directive")) //$NON-NLS-1$ |
| { |
| if (st.hasMoreTokens()) { |
| String directiveName = st.nextToken(); |
| if (directiveName.equals("taglib")) { //$NON-NLS-1$ |
| while (r != null && regions.hasNext() && !r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| r = (ITextRegion) regions.next(); |
| if (container.getText(r).equals(JSP11Namespace.ATTR_NAME_PREFIX)) { |
| String prefix = getAttributeValue(r, regions); |
| if (prefix != null) { |
| handleTaglib(prefix); |
| } |
| } |
| } |
| return; |
| } |
| else if (directiveName.equals("include")) { //$NON-NLS-1$ |
| |
| String fileLocation = ""; //$NON-NLS-1$ |
| |
| // skip to required "file" attribute, |
| // should be safe because |
| // "file" is the only attribute for the |
| // include directive |
| while (r != null && regions.hasNext() && !r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| r = (ITextRegion) regions.next(); |
| } |
| fileLocation = getAttributeValue(r, regions); |
| if (fileLocation != null) |
| handleIncludeFile(fileLocation); |
| } |
| else if (directiveName.equals("page")) { //$NON-NLS-1$ |
| |
| // bad if currentNode is referenced after |
| // here w/ the current list |
| // see: |
| // https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=3035 |
| // setCurrentNode(getCurrentNode().getNext()); |
| if (getCurrentNode() != null) { |
| // 'regions' contain the attrs |
| translatePageDirectiveAttributes(regions, getCurrentNode()); |
| } |
| } |
| else if (directiveName.equals("tag")) { //$NON-NLS-1$ |
| translatePageDirectiveAttributes(regions, getCurrentNode()); |
| } |
| else if (directiveName.equals("variable")) { //$NON-NLS-1$ |
| translateVariableDirectiveAttributes(regions); |
| } |
| } |
| } |
| else if (jspTagName.equals("include")) { //$NON-NLS-1$ |
| // <jsp:include page="filename") /> |
| checkAttributeValueContainer(regions, "page"); //$NON-NLS-1$ |
| } |
| else if (jspTagName.equals("forward")) { //$NON-NLS-1$ |
| checkAttributeValueContainer(regions, "page"); //$NON-NLS-1$ |
| } |
| else if (jspTagName.equals("param")) { //$NON-NLS-1$ |
| checkAttributeValueContainer(regions, "value"); //$NON-NLS-1$ |
| } |
| else if (jspTagName.equals("setProperty")) { //$NON-NLS-1$ |
| checkAttributeValueContainer(regions, "value"); //$NON-NLS-1$ |
| } |
| else if (jspTagName.equals("useBean")) //$NON-NLS-1$ |
| { |
| checkAttributeValueContainer(regions, "name"); //$NON-NLS-1$ |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=103004 |
| // advanceNextNode(); // get the content |
| if (getCurrentNode() != null) { |
| translateUseBean(container); // 'regions' |
| } |
| } |
| |
| } |
| } |
| else { |
| checkAllAttributeValueContainers(container,regions); |
| } |
| } |
| } |
| } |
| |
| /** |
| * translates embedded containers for ALL attribute values |
| * |
| * @param regions |
| */ |
| private void checkAllAttributeValueContainers(ITextRegionCollection container, Iterator regions) { |
| // tag name is not jsp |
| // handle embedded jsp attributes... |
| ITextRegion embedded = null; |
| // Iterator attrRegions = null; |
| // ITextRegion attrChunk = null; |
| ITextRegion prevRegion = null; |
| while (regions.hasNext()) { |
| embedded = (ITextRegion) regions.next(); |
| if (embedded.getType() == DOMRegionContext.XML_TAG_NAME || embedded.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) |
| |
| { |
| String fullTagName = container.getText(embedded); |
| if (fullTagName.indexOf(':') > -1 && !fullTagName.startsWith(JSP_PREFIX)) { |
| if (prevRegion != null) |
| addCustomTaglibVariables(fullTagName, container,prevRegion,-1); // it may be a custom tag |
| } |
| } |
| else if (embedded instanceof ITextRegionContainer) { |
| // parse out container |
| |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=130606 |
| // fix exponential iteration problem w/ embedded expressions |
| translateEmbeddedJSPInAttribute((ITextRegionContainer) embedded); |
| // attrRegions = ((ITextRegionContainer) |
| // embedded).getRegions().iterator(); |
| // while (attrRegions.hasNext()) { |
| // attrChunk = (ITextRegion) attrRegions.next(); |
| // String type = attrChunk.getType(); |
| // // embedded JSP in attribute support only want to |
| // // translate one time per |
| // // embedded region so we only translate on the JSP open |
| // // tags (not content) |
| // if (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN || |
| // type == |
| // DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || type == |
| // DOMJSPRegionContexts.JSP_DECLARATION_OPEN || type == |
| // DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || type == |
| // DOMJSPRegionContexts.JSP_EL_OPEN) { |
| // // now call jsptranslate |
| // translateEmbeddedJSPInAttribute((ITextRegionContainer) |
| // embedded); |
| // break; |
| // } |
| // } |
| } |
| prevRegion = embedded; |
| } |
| } |
| |
| /** |
| * translates embedded container for specified attribute |
| * |
| * @param regions |
| * @param attrName |
| */ |
| private void checkAttributeValueContainer(Iterator regions, String attrName) { |
| ITextRegion r = null; |
| while (regions.hasNext()) { |
| r = (ITextRegion) regions.next(); |
| if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME && getCurrentNode().getText(r).equals(attrName)) { |
| // skip to attribute value |
| while (regions.hasNext() && (r = (ITextRegion) regions.next()) != null) { |
| if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) |
| break; |
| } |
| // forces embedded region to be translated |
| if (r instanceof ContextRegionContainer) { |
| translateEmbeddedJSPInAttribute((ContextRegionContainer) r); |
| } |
| break; |
| } |
| } |
| } |
| |
| /* |
| * example: |
| * |
| * <jsp:scriptlet>scriptlet jsp-java content <![CDATA[ more jsp java ]]> |
| * jsp-java content... <![CDATA[ more jsp java ]]> </jsp:scriptlet> |
| * |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=93366 |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=88590 translate |
| * everything inbetween <scriptlet> tags, which may be more than one |
| * region (esp. CDATA) |
| * |
| */ |
| private void translateXMLJSPContent(int type) { |
| |
| IStructuredDocumentRegion sdr = getCurrentNode().getNext(); |
| int start = sdr.getStartOffset(); |
| int end = sdr.getEndOffset(); |
| String sdrText = ""; //$NON-NLS-1$ |
| |
| StringBuffer regionText = new StringBuffer(); |
| // read structured document regions until </jsp:scriptlet> or EOF |
| while (sdr != null && sdr.getType() != DOMRegionContext.XML_TAG_NAME) { |
| |
| // setup for next region |
| if (regionText.length() == 0) |
| start = sdr.getStartOffset(); |
| sdrText = sdr.getText(); |
| |
| if (sdr.getType() == DOMRegionContext.XML_CDATA_TEXT) { |
| // Clear out the buffer |
| if (regionText.length() > 0) { |
| writeToBuffer(type, regionText.toString(), start, end); |
| regionText = new StringBuffer(); |
| } |
| // just to be safe, make sure CDATA start & end are there |
| if (sdrText.startsWith("<![CDATA[") && sdrText.endsWith("]]>")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| start = sdr.getStartOffset() + 9; // <![CDATA[ |
| end = sdr.getEndOffset() - 3; // ]]> |
| sdrText = sdrText.substring(9, sdrText.length() - 3); |
| writeToBuffer(type, sdrText, start, end); |
| } |
| } |
| else { |
| // handle entity references |
| regionText.append(EscapedTextUtil.getUnescapedText(sdrText)); |
| end = sdr.getEndOffset(); |
| } |
| sdr = sdr.getNext(); |
| } |
| |
| if (regionText.length() > 0) |
| writeToBuffer(type, regionText.toString(), start, end); |
| setCurrentNode(sdr); |
| setSourceReferencePoint(); |
| } |
| |
| private void writeToBuffer(int type, String content, int jspStart, int jspEnd) { |
| switch (type) { |
| case SCRIPTLET : |
| translateScriptletString(content, getCurrentNode(), jspStart, jspEnd - jspStart, false); |
| break; |
| case EXPRESSION : |
| translateExpressionString(content, getCurrentNode(), jspStart, jspEnd - jspStart, false); |
| break; |
| case DECLARATION : |
| translateDeclarationString(content, getCurrentNode(), jspStart, jspEnd - jspStart, false); |
| break; |
| } |
| } |
| |
| /** |
| * Reparse and translate the contents of this XML comment as if it |
| * weren't. |
| */ |
| protected void translateXMLCommentNode(IStructuredDocumentRegion node) { |
| if (node.getNumberOfRegions() > 1 && DOMRegionContext.XML_COMMENT_OPEN.equals(node.getFirstRegion().getType())) { |
| ITextRegionList regions = node.getRegions(); |
| int startOffset = node.getStartOffset(regions.get(1)); |
| /* |
| * Make sure that an unclosed comment at the end of the file will |
| * still have its contents processed |
| */ |
| int endOffset = DOMRegionContext.XML_COMMENT_CLOSE.equals(node.getLastRegion().getType()) ? node.getStartOffset(node.getLastRegion()) : node.getEndOffset(node.getLastRegion()); |
| try { |
| String text = fStructuredDocument.get(startOffset, endOffset - startOffset); |
| decodeScriptBlock(text, startOffset); |
| } |
| catch (BadLocationException e) { |
| // Not sure *how* to recover from this... |
| Logger.logException(e); |
| } |
| } |
| } |
| |
| /** |
| * determines which type of JSP node to translate |
| */ |
| protected void translateJSPNode(ITextRegion region, Iterator regions, String type, int JSPType) { |
| if (type == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN && regions != null) { |
| translateDirective(regions); |
| } |
| else { |
| ITextRegionCollection contentRegion = null; |
| if (JSPType == STANDARD_JSP && (setCurrentNode(getCurrentNode().getNext())) != null) { |
| contentRegion = getCurrentNode(); |
| } |
| else if (JSPType == EMBEDDED_JSP && region instanceof ITextRegionCollection) { |
| translateEmbeddedJSPInBlock((ITextRegionCollection) region, regions); |
| // ensure the rest of this method won't be called |
| } |
| /* NOTE: the type here is of the node preceding the current node |
| * thus must check to see if the current node is JSP close, if it is |
| * then the JSP is something akin to <%%> and should not be translated |
| * (Bug 189318) |
| */ |
| if (contentRegion != null && contentRegion.getType() != DOMJSPRegionContexts.JSP_CLOSE) { |
| if (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN) { |
| translateExpression(contentRegion); |
| } |
| else if (type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN) { |
| translateDeclaration(contentRegion); |
| } |
| else if (type == DOMJSPRegionContexts.JSP_CONTENT || type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) { |
| translateScriptlet(contentRegion); |
| } |
| } |
| else { |
| // this is the case of an attribute w/ no region <p |
| // align="<%%>"> |
| setCursorOwner(getJSPTypeForRegion(region)); |
| } |
| } |
| } |
| |
| |
| private void translateEL(String elText, String delim, IStructuredDocumentRegion currentNode, int contentStart, int contentLength) { |
| IJSPELTranslator translator = getELTranslator(); |
| if (null != translator) { |
| List elProblems = translator.translateEL(elText, delim, currentNode, contentStart, contentLength, fUserELExpressions, fUserELRanges, fStructuredDocument); |
| fTranslationProblems.addAll(elProblems); |
| } |
| } |
| |
| /** |
| * Discover and instantiate an EL translator. |
| */ |
| public IJSPELTranslator getELTranslator() { |
| if (fELTranslator == null) { |
| |
| /* |
| * name of plugin that exposes this extension point |
| */ |
| IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(JSP_CORE_PLUGIN_ID, EL_TRANSLATOR_EXTENSION_NAME); // - |
| // extension |
| // id |
| |
| // Iterate over all declared extensions of this extension point. |
| // A single plug-in may extend the extension point more than once, |
| // although it's not recommended. |
| IConfigurationElement bestTranslator = null; |
| IExtension[] extensions = extensionPoint.getExtensions(); |
| for (int curExtension = 0; curExtension < extensions.length; curExtension++) { |
| IExtension extension = extensions[curExtension]; |
| |
| IConfigurationElement[] translators = extension.getConfigurationElements(); |
| for (int curTranslator = 0; curTranslator < translators.length; curTranslator++) { |
| |
| IConfigurationElement elTranslator = translators[curTranslator]; |
| |
| if (!EL_TRANSLATOR_EXTENSION_NAME.equals(elTranslator.getName())) { // - |
| // name |
| // of |
| // configElement |
| continue; |
| } |
| |
| String idString = elTranslator.getAttribute("id"); //$NON-NLS-1$ |
| if (null != idString && idString.equals(fELTranslatorID) || (null == bestTranslator && DEFAULT_JSP_EL_TRANSLATOR_ID.equals(idString))) { |
| bestTranslator = elTranslator; |
| } |
| } |
| } |
| |
| if (null != bestTranslator) { |
| try { |
| Object execExt = bestTranslator.createExecutableExtension("class"); //$NON-NLS-1$ |
| if (execExt instanceof IJSPELTranslator) { |
| return fELTranslator = (IJSPELTranslator) execExt; |
| } |
| } |
| catch (CoreException e) { |
| Logger.logException(e); |
| } |
| } |
| } |
| return fELTranslator; |
| } |
| |
| /** |
| * Pass the ITextRegionCollection which is the embedded region |
| * |
| * @param regions |
| * iterator for collection |
| */ |
| private void translateEmbeddedJSPInBlock(ITextRegionCollection collection, Iterator regions) { |
| ITextRegion region = null; |
| while (regions.hasNext()) { |
| region = (ITextRegion) regions.next(); |
| if (isJSP(region.getType())) |
| break; |
| region = null; |
| } |
| if (region != null) { |
| translateEmbeddedJSPInAttribute(collection); |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=126377 |
| // all of collection was translated so just finish off iterator |
| while (regions.hasNext()) |
| regions.next(); |
| } |
| } |
| |
| /* |
| * Translates all embedded jsp regions in embeddedContainer for example: |
| * <a href="index.jsp?p=<%=abc%>b=<%=xyz%>">abc</a> |
| */ |
| private void translateEmbeddedJSPInAttribute(ITextRegionCollection embeddedContainer) { |
| // THIS METHOD IS A FIX FOR |
| // jsp embedded in attribute regions |
| // loop all regions |
| ITextRegionList embeddedRegions = embeddedContainer.getRegions(); |
| ITextRegion delim = null; |
| ITextRegion content = null; |
| String type = null; |
| String quotetype = null; |
| final int length = embeddedRegions.size(); |
| for (int i = 0; i < length; i++) { |
| |
| // possible delimiter, check later |
| delim = embeddedRegions.get(i); |
| type = delim.getType(); |
| if (type == DOMRegionContext.XML_TAG_NAME ) { |
| String fullTagName = embeddedContainer.getText(delim); |
| if (fullTagName.indexOf(':') > -1 && !fullTagName.startsWith(JSP_PREFIX)) { |
| ITextRegion prevRegion =null; |
| if (i>0) |
| prevRegion = embeddedRegions.get(i-1); |
| addCustomTaglibVariables(fullTagName, embeddedContainer,prevRegion,i+1); // it may be a custom tag |
| } |
| } |
| if(type == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_DQUOTE || type == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_SQUOTE |
| || type == DOMJSPRegionContexts.JSP_TAG_ATTRIBUTE_VALUE_DQUOTE || type == DOMJSPRegionContexts.JSP_TAG_ATTRIBUTE_VALUE_SQUOTE) |
| quotetype = type; |
| |
| // check next region to see if it's content |
| if (i + 1 < embeddedRegions.size()) { |
| String regionType = embeddedRegions.get(i + 1).getType(); |
| if (regionType == DOMJSPRegionContexts.JSP_CONTENT || regionType == DOMJSPRegionContexts.JSP_EL_CONTENT) |
| content = embeddedRegions.get(i + 1); |
| } |
| |
| if (content != null) { |
| int contentStart = embeddedContainer.getStartOffset(content); |
| int rStart = fCurrentNode.getStartOffset() + contentStart; |
| int rEnd = fCurrentNode.getStartOffset() + embeddedContainer.getEndOffset(content); |
| |
| boolean inThisRegion = rStart <= fSourcePosition && rEnd >= fSourcePosition; |
| // int jspPositionStart = fCurrentNode.getStartOffset() + |
| // contentStart; |
| |
| if (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN) { |
| fLastJSPType = EXPRESSION; |
| // translateExpressionString(embeddedContainer.getText(content), |
| // fCurrentNode, contentStart, content.getLength()); |
| translateExpressionString(embeddedContainer.getText(content), embeddedContainer, contentStart, content.getLength(), quotetype); |
| } |
| else if (type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) { |
| fLastJSPType = SCRIPTLET; |
| // translateScriptletString(embeddedContainer.getText(content), |
| // fCurrentNode, contentStart, content.getLength()); |
| translateScriptletString(embeddedContainer.getText(content), embeddedContainer, contentStart, content.getLength(), false); |
| } |
| else if (type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN) { |
| fLastJSPType = DECLARATION; |
| // translateDeclarationString(embeddedContainer.getText(content), |
| // fCurrentNode, contentStart, content.getLength()); |
| translateDeclarationString(embeddedContainer.getText(content), embeddedContainer, contentStart, content.getLength(), false); |
| } |
| else if (type == DOMJSPRegionContexts.JSP_EL_OPEN || type == DOMJSPRegionContexts.JSP_VBL_OPEN) { |
| fLastJSPType = EXPRESSION; |
| ITextRegion region = null; |
| |
| int start = delim.getEnd(); |
| while (++i < length) { |
| region = embeddedRegions.get(i); |
| if (region == null || !isELType(region.getType())) |
| break; |
| } |
| final String elText = embeddedContainer.getFullText().substring(start, (region != null ? region.getStart() : embeddedContainer.getLength() - 1)); |
| translateEL(elText, embeddedContainer.getText(delim), fCurrentNode, embeddedContainer.getEndOffset(delim), elText.length()); |
| } |
| |
| // calculate relative offset in buffer |
| if (inThisRegion) { |
| setCursorOwner(fLastJSPType); |
| int currentBufferLength = getCursorOwner().length(); |
| setRelativeOffset((fSourcePosition - contentStart) + currentBufferLength); |
| if (fLastJSPType == EXPRESSION) { |
| // if an expression, add then length of the enclosing |
| // paren.. |
| setCursorInExpression(true); |
| setRelativeOffset(getRelativeOffset() + EXPRESSION_PREFIX.length()); |
| } |
| } |
| } |
| else { |
| type = null; |
| } |
| } |
| } |
| |
| private int fLastJSPType = SCRIPTLET; |
| |
| /** |
| * Whether the superclass has been explictly set via the document contents |
| */ |
| private boolean fIsDefaultSuperclass; |
| |
| /** |
| * JSPType is only used internally in this class to describe tye type of |
| * region to be translated |
| * |
| * @param region |
| * @return int |
| */ |
| private int getJSPTypeForRegion(ITextRegion region) { |
| String regionType = region.getType(); |
| int type = SCRIPTLET; |
| if (regionType == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) |
| type = SCRIPTLET; |
| else if (regionType == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN) |
| type = EXPRESSION; |
| else if (regionType == DOMJSPRegionContexts.JSP_DECLARATION_OPEN) |
| type = DECLARATION; |
| else if (regionType == DOMJSPRegionContexts.JSP_CONTENT) |
| type = fLastJSPType; |
| // remember the last type, in case the next type that comes in is |
| // JSP_CONTENT |
| fLastJSPType = type; |
| return type; |
| } |
| |
| /** |
| * /* <%@ %> /* need to pass in the directive tag region |
| */ |
| protected void translateDirective(Iterator regions) { |
| ITextRegion r = null; |
| String regionText, attrValue = ""; //$NON-NLS-1$ |
| while (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) { // could |
| // be |
| // XML_CONTENT |
| // = |
| // "", |
| // skips |
| // attrs? |
| regionText = getCurrentNode().getText(r); |
| if (regionText.equals("taglib")) { //$NON-NLS-1$ |
| // add custom tag block markers here |
| handleTaglib(); |
| return; |
| } |
| else if (regionText.equals("include")) { //$NON-NLS-1$ |
| String fileLocation = ""; //$NON-NLS-1$ |
| // CMVC 258311 |
| // PMR 18368, B663 |
| // skip to required "file" attribute, should be safe because |
| // "file" is the only attribute for the include directive |
| while (r != null && regions.hasNext() && !r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| r = (ITextRegion) regions.next(); |
| } |
| fileLocation = getAttributeValue(r, regions); |
| if (attrValue != null) |
| handleIncludeFile(fileLocation); |
| } |
| else if (regionText.equals("page")) { //$NON-NLS-1$ |
| translatePageDirectiveAttributes(regions, getCurrentNode()); |
| } |
| else if (regionText.equals("tag")) { //$NON-NLS-1$ |
| // some attributes overlap, so both are handled in this method |
| translatePageDirectiveAttributes(regions, getCurrentNode()); |
| } |
| else if (regionText.equals("variable")) { //$NON-NLS-1$ |
| translateVariableDirectiveAttributes(regions); |
| } |
| else if (regionText.equals("attribute")) { //$NON-NLS-1$ |
| translateAttributeDirectiveAttributes(regions); |
| } |
| } |
| } |
| |
| private void translateAttributeDirectiveAttributes(Iterator regions) { |
| ITextRegion r = null; |
| String attrName, attrValue; |
| |
| String varType = "java.lang.String"; //$NON-NLS-1$ // the default class... |
| String varName = null; |
| String description = "";//$NON-NLS-1$ |
| boolean isFragment = false; |
| |
| // iterate all attributes |
| while (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() != DOMJSPRegionContexts.JSP_CLOSE) { |
| attrName = attrValue = null; |
| if (r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| attrName = getCurrentNode().getText(r).trim(); |
| if (attrName.length() > 0) { |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| attrValue = StringUtils.strip(getCurrentNode().getText(r)); |
| } |
| // has equals, but no value? |
| } |
| if (attrName.equals(JSP11Namespace.ATTR_NAME_TYPE)) { |
| varType = attrValue; |
| } |
| else if (attrName.equals(JSP20Namespace.ATTR_NAME_FRAGMENT)) { |
| isFragment = Boolean.valueOf(attrValue).booleanValue(); |
| } |
| else if (attrName.equals(JSP11Namespace.ATTR_NAME_NAME)) { |
| varName = attrValue; |
| } |
| else if (attrName.equals(JSP20Namespace.ATTR_NAME_DESCRIPTION)) { |
| description = attrValue; |
| } |
| } |
| } |
| } |
| if (varName != null) { |
| if (isFragment) { |
| // 2.0:JSP.8.5.2 |
| varType = fServletAPIDescriptor.getRootPackage() + ".jsp.tagext.JspFragment"; //$NON-NLS-1$ |
| } |
| String declaration = new TaglibVariable(varType, varName, "", description).getDeclarationString(true, fContext, TaglibVariable.M_PRIVATE); //$NON-NLS-1$ |
| appendToBuffer(declaration, fUserDeclarations, false, fCurrentNode); |
| } |
| } |
| |
| private void translateVariableDirectiveAttributes(Iterator regions) { |
| /* |
| * Shouldn't create a scripting variable in *this* tag file's |
| * translation, only in JSP files that use it - |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=188780 |
| */ |
| } |
| |
| /* |
| * This method should ideally only be called once per run through |
| * JSPTranslator This is intended for use by inner helper classes that |
| * need to add block markers to their own parsers. This method only adds |
| * markers that came from <@taglib> directives, (not <@include>), since |
| * include file taglibs are handled on the fly when they are encountered. * |
| * @param regions |
| * |
| * @deprecated - does not properly handle prefixes |
| */ |
| protected void handleTaglib() { |
| // get/create TLDCMDocument |
| TLDCMDocumentManager mgr = TaglibController.getTLDCMDocumentManager(fStructuredDocument); |
| if (mgr != null) { |
| List trackers = mgr.getCMDocumentTrackers(getCurrentNode().getEnd()); |
| Iterator it = trackers.iterator(); |
| CMDocumentTracker tracker = null; |
| Iterator taglibRegions = null; |
| IStructuredDocumentRegion sdRegion = null; |
| ITextRegion r = null; |
| while (it.hasNext()) { |
| tracker = (CMDocumentTracker) it.next(); |
| sdRegion = tracker.getStructuredDocumentRegion(); |
| // since may be call from another thread (like a background |
| // job) |
| // this check is to be safer |
| if (sdRegion != null && !sdRegion.isDeleted()) { |
| taglibRegions = sdRegion.getRegions().iterator(); |
| while (!sdRegion.isDeleted() && taglibRegions.hasNext()) { |
| r = (ITextRegion) taglibRegions.next(); |
| if (r.getType().equals(DOMJSPRegionContexts.JSP_DIRECTIVE_NAME)) { |
| String text = sdRegion.getText(r); |
| if (JSP12TLDNames.TAGLIB.equals(text) || JSP12Namespace.ElementName.DIRECTIVE_TAGLIB.equals(text)) { |
| addBlockMarkers(tracker.getDocument()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * This method should ideally only be called once per run through |
| * JSPTranslator This is intended for use by inner helper classes that |
| * need to add block markers to their own parsers. This method only adds |
| * markers that came from <@taglib> directives, (not <@include>), since |
| * include file taglibs are handled on the fly when they are encountered. * |
| * @param regions |
| */ |
| private void handleTaglib(String prefix) { |
| // get/create TLDCMDocument |
| TLDCMDocumentManager mgr = TaglibController.getTLDCMDocumentManager(fStructuredDocument); |
| if (mgr != null) { |
| // get trackers for the CMDocuments enabled at this offset |
| List trackers = mgr.getCMDocumentTrackers(getCurrentNode().getEnd()); |
| Iterator it = trackers.iterator(); |
| CMDocumentTracker tracker = null; |
| while (it.hasNext()) { |
| tracker = (CMDocumentTracker) it.next(); |
| addBlockMarkers(prefix + ":", tracker.getDocument()); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /* |
| * adds block markers to JSPTranslator's block marker list for all |
| * elements in doc @param doc |
| */ |
| protected void addBlockMarkers(CMDocument doc) { |
| if (doc.getElements().getLength() > 0) { |
| Iterator elements = doc.getElements().iterator(); |
| CMNode node = null; |
| while (elements.hasNext()) { |
| node = (CMNode) elements.next(); |
| getBlockMarkers().add(new BlockMarker(node.getNodeName(), null, DOMJSPRegionContexts.JSP_CONTENT, true)); |
| } |
| } |
| } |
| |
| /* |
| * adds block markers to JSPTranslator's block marker list for all |
| * elements in doc @param doc |
| */ |
| protected void addBlockMarkers(String prefix, CMDocument doc) { |
| if (doc.getElements().getLength() > 0) { |
| Iterator elements = doc.getElements().iterator(); |
| CMNode node = null; |
| while (elements.hasNext()) { |
| node = (CMNode) elements.next(); |
| if (node instanceof TLDElementDeclaration && ((TLDElementDeclaration) node).getBodycontent().equals(JSP12TLDNames.CONTENT_TAGDEPENDENT)) |
| getBlockMarkers().add(new BlockMarker(prefix + node.getNodeName(), null, DOMRegionContext.BLOCK_TEXT, true)); |
| else |
| getBlockMarkers().add(new BlockMarker(prefix + node.getNodeName(), null, DOMJSPRegionContexts.JSP_CONTENT, true)); |
| } |
| } |
| } |
| |
| /** |
| * If r is an attribute name region, this method will safely return the |
| * value for that attribute. |
| * |
| * @param r |
| * @param remainingRegions |
| * @return the value for the attribute name (r), or null if isn't one |
| */ |
| protected String getAttributeValue(ITextRegion r, Iterator remainingRegions) { |
| if (r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| if (remainingRegions.hasNext() && (r = (ITextRegion) remainingRegions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| if (remainingRegions.hasNext() && (r = (ITextRegion) remainingRegions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| return StringUtils.stripQuotes(getCurrentNode().getText(r)); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * takes an iterator of the attributes of a page directive and the |
| * directive itself. The iterator is used in case it can be optimized, but |
| * the documentRegion is still required to ensure that the values are |
| * extracted from the correct text. |
| */ |
| protected void translatePageDirectiveAttributes(Iterator regions, IStructuredDocumentRegion documentRegion) { |
| ITextRegion r = null; |
| String attrName, attrValue; |
| // iterate all attributes |
| while (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() != DOMJSPRegionContexts.JSP_CLOSE) { |
| attrName = attrValue = null; |
| if (r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| |
| attrName = documentRegion.getText(r).trim(); |
| if (attrName.length() > 0) { |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| |
| attrValue = StringUtils.strip(documentRegion.getText(r)); |
| } |
| // has equals, but no value? |
| } |
| setDirectiveAttribute(attrName, attrValue); |
| } |
| } |
| } |
| } |
| |
| /** |
| * sets the appropriate page/tag directive attribute |
| */ |
| protected void setDirectiveAttribute(String attrName, String attrValue) { |
| if (attrValue == null) |
| return; // uses default (if there was one) |
| if (attrName.equals("extends")) //$NON-NLS-1$ |
| { |
| fSuperclass = attrValue; |
| fIsDefaultSuperclass = false; |
| } |
| else if (attrName.equals("import")) //$NON-NLS-1$ |
| { |
| addImports(attrValue); |
| } |
| else if (attrName.equals("session")) //$NON-NLS-1$ |
| { |
| fIsInASession = Boolean.valueOf(attrValue).booleanValue(); |
| } |
| else if (attrName.equals("buffer")) //$NON-NLS-1$ |
| { |
| // ignore for now |
| } |
| else if (attrName.equals("autoFlush")) //$NON-NLS-1$ |
| { |
| // ignore for now |
| } |
| else if (attrName.equals("isThreadSafe")) //$NON-NLS-1$ |
| { |
| // fThreadSafe = "true".equalsIgnoreCase(attrValue); //$NON-NLS-1$ |
| } |
| else if (attrName.equals("isErrorPage")) //$NON-NLS-1$ |
| { |
| fIsErrorPage = Boolean.valueOf(attrValue).booleanValue(); |
| } |
| } |
| |
| protected void handleIncludeFile(String filename) { |
| if (filename != null && fProcessIncludes) { |
| IPath modelPath = getModelPath(); |
| IPath basePath = modelPath; |
| if (basePath != null) { |
| /* |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=227576 |
| * |
| * The resolution of the included fragment should use the file |
| * containing the directive as the base reference, not always |
| * the main JSP being invoked. Verified behavior with Apache |
| * Tomcat 5.5.20. |
| */ |
| if (!getIncludes().isEmpty()) { |
| basePath = new Path((String) getIncludes().peek()); |
| } |
| String filePathString = FacetModuleCoreSupport.resolve(basePath, filename).toString(); |
| fIncludedPaths.add(filePathString); |
| |
| if (!getIncludes().contains(filePathString) && !filePathString.equals(modelPath.toString())) { |
| getIncludes().push(filePathString); |
| JSPIncludeRegionHelper helper = new JSPIncludeRegionHelper(this, true); |
| // Should we consider preludes on this segment? |
| helper.parse(filePathString); |
| getIncludes().pop(); |
| } |
| } |
| } |
| } |
| |
| private Stack getIncludes() { |
| if (fIncludes == null) |
| fIncludes = new Stack(); |
| return fIncludes; |
| } |
| |
| public Collection getIncludedPaths() { |
| return fIncludedPaths; |
| } |
| |
| protected void translateExpressionString(String newText, ITextRegionCollection embeddedContainer, int jspPositionStart, int jspPositionLength, boolean isIndirect) { |
| appendToBuffer(EXPRESSION_PREFIX, fUserCode, false, embeddedContainer, true); |
| appendToBuffer(newText, fUserCode, true, embeddedContainer, jspPositionStart, jspPositionLength, isIndirect, true); |
| appendToBuffer(EXPRESSION_SUFFIX, fUserCode, false, embeddedContainer); |
| } |
| |
| protected void translateExpressionString(String newText, ITextRegionCollection embeddedContainer, int jspPositionStart, int jspPositionLength, String quotetype) { |
| if(quotetype == null || quotetype == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_DQUOTE ||quotetype == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_SQUOTE ) { |
| translateExpressionString(newText, embeddedContainer, jspPositionStart, jspPositionLength, false); |
| return; |
| } |
| |
| //-- This is a quoted attribute. We need to unquote as per the JSP spec: JSP 2.0 page 1-36 |
| appendToBuffer(EXPRESSION_PREFIX, fUserCode, false, embeddedContainer, true); |
| |
| int length = newText.length(); |
| int runStart = 0; |
| int i = 0; |
| for ( ; i < length; i++) { |
| //-- collect a new run |
| char c = newText.charAt(i); |
| if (c == '\\') { |
| //-- Escaped value. Add the run, then unescape |
| int runLength = i-runStart; |
| if (runLength > 0) { |
| appendToBuffer(newText.substring(runStart, i), fUserCode, true, embeddedContainer, jspPositionStart, runLength, false, true); |
| jspPositionStart += runLength + 1; |
| jspPositionLength -= runLength + 1; |
| } |
| runStart = ++i; |
| if (i >= length) { // Escape but no data follows?! |
| //- error. |
| break; |
| } |
| c = newText.charAt(i); // The escaped character, copied verbatim |
| } |
| } |
| //-- Copy last-run |
| int runLength = i - runStart; |
| if (runLength > 0) |
| appendToBuffer(newText.substring(runStart, i), fUserCode, true, embeddedContainer, jspPositionStart, runLength, false, false); |
| appendToBuffer(EXPRESSION_SUFFIX, fUserCode, false, embeddedContainer); |
| } |
| |
| protected void translateDeclarationString(String newText, ITextRegionCollection embeddedContainer, int jspPositionStart, int jspPositionLength, boolean isIndirect) { |
| appendToBuffer(newText, fUserDeclarations, true, embeddedContainer, jspPositionStart, jspPositionLength, isIndirect); |
| appendToBuffer(ENDL, fUserDeclarations, false, embeddedContainer); |
| } |
| |
| /** |
| * used by XMLJSPRegionHelper for included JSP files |
| * |
| * @param newText |
| * @param embeddedContainer |
| * @param jspPositionStart |
| * @param jspPositionLength |
| */ |
| protected void translateScriptletString(String newText, ITextRegionCollection embeddedContainer, int jspPositionStart, int jspPositionLength, boolean isIndirect) { |
| appendToBuffer(newText, fUserCode, true, embeddedContainer, jspPositionStart, jspPositionLength, isIndirect); |
| } |
| |
| // the following 3 methods determine the cursor position |
| // <%= %> |
| protected void translateExpression(ITextRegionCollection region) { |
| String newText = getUnescapedRegionText(region, EXPRESSION); |
| appendToBuffer(EXPRESSION_PREFIX, fUserCode, false, region, true); |
| appendToBuffer(newText, fUserCode, true, region, true); |
| appendToBuffer(EXPRESSION_SUFFIX, fUserCode, false, region); |
| } |
| |
| // |
| // <%! %> |
| protected void translateDeclaration(ITextRegionCollection region) { |
| String newText = getUnescapedRegionText(region, DECLARATION); |
| appendToBuffer(newText, fUserDeclarations, true, region); |
| appendToBuffer(ENDL, fUserDeclarations, false, region); |
| } |
| |
| // |
| // <% %> |
| protected void translateScriptlet(ITextRegionCollection region) { |
| String newText = getUnescapedRegionText(region, SCRIPTLET); |
| appendToBuffer(newText, fUserCode, true, region); |
| } |
| |
| /** |
| * Append using a region, probably indirect mapping (eg. <%@page |
| * include=""%>) |
| * |
| * @param newText |
| * @param buffer |
| * @param addToMap |
| * @param jspReferenceRegion |
| */ |
| private void appendToBuffer(String newText, StringBuffer buffer, boolean addToMap, ITextRegionCollection jspReferenceRegion) { |
| int start = 0, length = 0; |
| if (jspReferenceRegion != null) { |
| start = jspReferenceRegion.getStartOffset(); |
| length = jspReferenceRegion.getLength(); |
| } |
| appendToBuffer(newText, buffer, addToMap, jspReferenceRegion, start, length, false); |
| } |
| |
| /** |
| * Append using a region, probably indirect mapping (eg. <%@page |
| * include=""%>) |
| * |
| * @param newText |
| * @param buffer |
| * @param addToMap |
| * @param jspReferenceRegion |
| * @param nonl |
| */ |
| private void appendToBuffer(String newText, StringBuffer buffer, boolean addToMap, ITextRegionCollection jspReferenceRegion, boolean nonl) { |
| int start = 0, length = 0; |
| if (jspReferenceRegion != null) { |
| start = jspReferenceRegion.getStartOffset(); |
| length = jspReferenceRegion.getLength(); |
| } |
| appendToBuffer(newText, buffer, addToMap, jspReferenceRegion, start, length, false, nonl); |
| } |
| |
| private void appendToBuffer(String newText, StringBuffer buffer, boolean addToMap, ITextRegionCollection jspReferenceRegion, int jspPositionStart, int jspPositionLength, boolean isIndirect) { |
| appendToBuffer(newText, buffer, addToMap, jspReferenceRegion, jspPositionStart, jspPositionLength, isIndirect, false); |
| } |
| |
| |
| /** |
| * Adds newText to the buffer passed in, and adds to translation mapping |
| * as specified by the addToMap flag. some special cases to consider (that |
| * may be affected by changes to this method): included files scriplets in |
| * an attribute value refactoring |
| * |
| * @param newText |
| * @param buffer |
| * @param addToMap |
| */ |
| private void appendToBuffer(String newText, StringBuffer buffer, boolean addToMap, ITextRegionCollection jspReferenceRegion, int jspPositionStart, int jspPositionLength, boolean isIndirect, boolean nonl) { |
| |
| int origNewTextLength = newText.length(); |
| |
| // nothing to append |
| if (jspReferenceRegion == null) |
| return; |
| |
| // add a newline so translation looks cleaner |
| if (! nonl && !newText.endsWith(ENDL)) |
| newText += ENDL; |
| |
| //dump any non translated code before writing translated code |
| writePlaceHolderForNonTranslatedCode(); |
| |
| //if appending to the buffer can assume something got translated |
| fCodeTranslated = true; |
| |
| if (buffer == fUserCode) { |
| buffer.append(newText); |
| if (addToMap) { |
| if (isUsebeanTag(jspReferenceRegion)) { |
| try { |
| // requires special mapping |
| appendUseBeanToBuffer(newText, jspReferenceRegion, isIndirect); |
| } |
| catch (Exception e) { |
| // still working out kinks |
| Logger.logException(e); |
| } |
| } |
| else { |
| // all other cases |
| Position javaRange = new Position(fOffsetInUserCode, origNewTextLength); |
| Position jspRange = new Position(jspPositionStart, jspPositionLength); |
| |
| fCodeRanges.put(javaRange, jspRange); |
| if (isIndirect) |
| fIndirectRanges.put(javaRange, jspRange); |
| } |
| } |
| fOffsetInUserCode += newText.length(); |
| } |
| else if (buffer == fUserDeclarations) { |
| buffer.append(newText); |
| if (addToMap) { |
| Position javaRange = new Position(fOffsetInUserDeclarations, newText.length()); |
| Position jspRange = new Position(jspPositionStart, jspPositionLength); |
| |
| fDeclarationRanges.put(javaRange, jspRange); |
| if (isIndirect) |
| fIndirectRanges.put(javaRange, jspRange); |
| } |
| fOffsetInUserDeclarations += newText.length(); |
| } |
| } |
| |
| /** |
| * |
| * @param jspReferenceRegion |
| * @return |
| */ |
| private boolean isUsebeanTag(ITextRegionCollection jspReferenceRegion) { |
| ITextRegionList regions = jspReferenceRegion.getRegions(); |
| ITextRegion r = null; |
| boolean isUseBean = false; |
| for (int i = 0; i < regions.size(); i++) { |
| r = regions.get(i); |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=128490 |
| // length of 11 is the length of jsp:useBean |
| // and saves the expensive getText.equals call |
| if (r.getType() == DOMRegionContext.XML_TAG_NAME) { |
| if (r.getTextLength() == 11 && jspReferenceRegion.getText(r).equals("jsp:useBean")) { //$NON-NLS-1$ |
| isUseBean = true; |
| } |
| // break no matter what if you hit tagname |
| break; |
| } |
| } |
| return isUseBean; |
| } |
| |
| /** |
| * @param importName |
| * should be just the package plus the type eg. java.util.List |
| * or java.util.* |
| * @param jspReferenceRegion |
| * should be the <%@ page import = "java.util.List"%> region |
| * @param addToMap |
| */ |
| private void appendImportToBuffer(String importName, ITextRegionCollection jspReferenceRegion, boolean addToMap) { |
| String javaImportString = "import " + importName + ";" + ENDL; //$NON-NLS-1$ //$NON-NLS-2$ |
| fUserImports.append(javaImportString); |
| if (addToMap) { |
| addImportToMap(importName, jspReferenceRegion); |
| } |
| fOffsetInUserImports += javaImportString.length(); |
| } |
| |
| /** |
| * new text can be something like: "import java.lang.Object;\n" |
| * |
| * but the reference region could have been something like: <%@page |
| * import="java.lang.Object, java.io.*, java.util.List"%> |
| * |
| * so the exact mapping has to be calculated carefully. |
| * |
| * isIndirect means that the import came from an included file (if true) |
| * |
| * @param importName |
| * @param jspReferenceRegion |
| */ |
| private void addImportToMap(String importName, ITextRegionCollection jspReferenceRegion) { |
| |
| // massage text |
| // String jspText = importName.substring(importName.indexOf("import ") |
| // + 7, importName.indexOf(';')); |
| // String jspText = importName.trim(); |
| |
| // these positions will be updated below |
| Position javaRange = new Position(fOffsetInUserImports + 7, 1); |
| Position jspRange = new Position(jspReferenceRegion.getStart(), jspReferenceRegion.getLength()); |
| |
| // calculate JSP range by finding "import" attribute |
| ITextRegionList regions = jspReferenceRegion.getRegions(); |
| int size = regions.size(); |
| |
| int start = -1; |
| int length = -1; |
| |
| ITextRegion r = null; |
| for (int i = 0; i < size; i++) { |
| r = regions.get(i); |
| if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) |
| if (jspReferenceRegion.getText(r).trim().equals("import")) { //$NON-NLS-1$ |
| // get the attr value region |
| if (size > i + 2) { |
| r = regions.get(i + 2); |
| if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| |
| String jspImportText = jspReferenceRegion.getText(r); |
| |
| // the position in question (in the JSP) is what |
| // is bracketed below |
| // includes whitespace |
| // <%@page import="java.lang.Object,[ java.io.* ], |
| // java.util.List"%> |
| |
| // in the java file |
| // import [ java.io.* ]; |
| |
| start = jspImportText.indexOf(importName); |
| length = importName.length(); |
| |
| // safety, don't add to map if bad positioning |
| if (start == -1 || length < 1) |
| break; |
| |
| // update jsp range |
| jspRange.setOffset(jspReferenceRegion.getStartOffset(r) + start); |
| jspRange.setLength(length); |
| |
| // update java range |
| javaRange.setLength(length); |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| // safety for bad ranges |
| if (start != -1 && length > 1) { |
| // put ranges in java -> jsp range map |
| fImportRanges.put(javaRange, jspRange); |
| } |
| } |
| |
| /** |
| * temp fix for 282295 until better mapping is in place |
| * |
| * @param newText |
| * @param jspReferenceRegion |
| */ |
| private void appendUseBeanToBuffer(String newText, ITextRegionCollection jspReferenceRegion, boolean isIndirect) throws Exception { |
| // java string looks like this (tokenized) |
| // Type id = new Classname();\n |
| // 0 1 2 3 4 |
| // or |
| // Type id = null;\n // if there is no classname |
| // 0 1 2 3 |
| |
| // ---------------------- |
| // calculate java ranges |
| // ---------------------- |
| StringTokenizer st = new StringTokenizer(newText, " ", false); //$NON-NLS-1$ |
| int i = 0; |
| String[] parsedJava = new String[st.countTokens()]; |
| while (st.hasMoreTokens()) |
| parsedJava[i++] = st.nextToken(); |
| |
| String type = parsedJava[0] != null ? parsedJava[0] : ""; //$NON-NLS-1$ |
| String id = parsedJava[1] != null ? parsedJava[1] : ""; //$NON-NLS-1$ |
| String className = parsedJava.length > 4 ? parsedJava[4] : ""; //$NON-NLS-1$ |
| |
| Position javaTypeRange = new Position(fOffsetInUserCode, type.length()); |
| Position javaIdRange = new Position(fOffsetInUserCode + type.length() + 1, id.length()); |
| Position javaClassRange = new Position(fOffsetInUserCode + type.length() + 1 + id.length() + 7, 0); |
| /* |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=212242 - Check for |
| * the existence of '(' first. |
| */ |
| int parenPos = -1; |
| if (className.length() >= 4 && (parenPos = className.indexOf('(')) >= 0) { |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=86132 |
| int classNameLength = className.substring(0, parenPos).length(); |
| javaClassRange = new Position(fOffsetInUserCode + type.length() + 1 + id.length() + 7, classNameLength); |
| } |
| |
| // --------------------- |
| // calculate jsp ranges |
| // --------------------- |
| ITextRegionList regions = jspReferenceRegion.getRegions(); |
| ITextRegion r = null; |
| String attrName = "", attrValue = ""; //$NON-NLS-1$ //$NON-NLS-2$ |
| int quoteOffset = 0; |
| Position jspTypeRange = null; |
| Position jspIdRange = null; |
| Position jspClassRange = null; |
| |
| for (int j = 0; j < regions.size(); j++) { |
| r = regions.get(j); |
| if (r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { |
| attrName = jspReferenceRegion.getText(r); |
| if (regions.size() > j + 2 && regions.get(j + 2).getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| // get attr value |
| r = regions.get(j + 2); |
| attrValue = jspReferenceRegion.getText(r); |
| |
| // may have quotes |
| quoteOffset = (attrValue.startsWith("\"") || attrValue.startsWith("'")) ? 1 : 0; //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| if (attrName.equals("type")) //$NON-NLS-1$ |
| jspTypeRange = new Position(jspReferenceRegion.getStartOffset(r) + quoteOffset, StringUtils.stripQuotesLeaveInsideSpace(attrValue).length()); |
| else if (attrName.equals("id")) //$NON-NLS-1$ |
| jspIdRange = new Position(jspReferenceRegion.getStartOffset(r) + quoteOffset, StringUtils.stripQuotesLeaveInsideSpace(attrValue).length()); |
| else if (attrName.equals("class")) //$NON-NLS-1$ |
| jspClassRange = new Position(jspReferenceRegion.getStartOffset(r) + quoteOffset, StringUtils.stripQuotesLeaveInsideSpace(attrValue).length()); |
| } |
| } |
| } |
| |
| // put ranges in java -> jsp range map |
| if (!type.equals("") && jspTypeRange != null) { //$NON-NLS-1$ |
| fCodeRanges.put(javaTypeRange, jspTypeRange); |
| // note: don't update offsets for this map when result is built |
| // they'll be updated when code ranges offsets are updated |
| fUseBeanRanges.put(javaTypeRange, jspTypeRange); |
| if (isIndirect) |
| fIndirectRanges.put(javaTypeRange, jspTypeRange); |
| } |
| if (!id.equals("") && jspIdRange != null) { //$NON-NLS-1$ |
| fCodeRanges.put(javaIdRange, jspIdRange); |
| // note: don't update offsets for this map when result is built |
| // they'll be updated when code ranges offsets are updated |
| fUseBeanRanges.put(javaIdRange, jspTypeRange); |
| if (isIndirect) |
| fIndirectRanges.put(javaIdRange, jspTypeRange); |
| } |
| if (!className.equals("") && jspClassRange != null) { //$NON-NLS-1$ |
| fCodeRanges.put(javaClassRange, jspClassRange); |
| // note: don't update offsets for this map when result is built |
| // they'll be updated when code ranges offsets are updated |
| fUseBeanRanges.put(javaClassRange, jspTypeRange); |
| if (isIndirect) |
| fIndirectRanges.put(javaClassRange, jspTypeRange); |
| } |
| } |
| |
| /** |
| * Set the buffer to the current JSPType: STANDARD_JSP, EMBEDDED_JSP, |
| * DECLARATION, EXPRESSION, SCRIPTLET (for keepting track of cursor |
| * position when the final document is built) |
| * |
| * @param JSPType |
| * the JSP type that the cursor is in |
| */ |
| protected void setCursorOwner(int JSPType) { |
| switch (JSPType) { |
| case DECLARATION : |
| setCursorOwner(fUserDeclarations); |
| break; |
| case EXPRESSION : |
| case SCRIPTLET : |
| setCursorOwner(fUserCode); |
| break; |
| default : |
| setCursorOwner(fUserCode); |
| } |
| } |
| |
| /** |
| * this piece of code iterates through fCurrentNodes and clumps them |
| * together in a big text string - unescaping characters if they are not |
| * CDATA - simply appending if they are CDATA it stops iteration when it |
| * hits a node that is an XML_TAG_NAME (which should be the region closing |
| * tag) |
| */ |
| protected String getUnescapedRegionText(ITextRegionCollection stRegion, int JSPType) { |
| StringBuffer buffer = new StringBuffer(); |
| int start = stRegion.getStartOffset(); |
| int end = stRegion.getEndOffset(); |
| // adjustment necessary for embedded region containers |
| if (stRegion instanceof ITextRegionContainer && stRegion.getType() == DOMRegionContext.BLOCK_TEXT) { |
| if (stRegion.getRegions() != null && stRegion.getRegions().size() > 1) { |
| ITextRegion jspContent = stRegion.getRegions().get(1); // should |
| // be |
| // jspContent |
| // region |
| start = stRegion.getStartOffset(jspContent); |
| end = stRegion.getEndOffset(jspContent); |
| } |
| } |
| int CDATAOffset = 0; // number of characters lost in conversion |
| int bufferSize = 0; |
| if (stRegion.getType() == DOMJSPRegionContexts.JSP_CONTENT || stRegion.getType() == DOMRegionContext.BLOCK_TEXT // need |
| // this |
| // for |
| // embedded |
| // JSP |
| // regions |
| || stRegion.getType() == DOMRegionContext.XML_TAG_NAME) // need |
| // this |
| // in |
| // case |
| // there's |
| // no |
| // region... |
| { |
| fInCodeRegion = (start <= fSourcePosition && fSourcePosition <= end); |
| if (fInCodeRegion) { |
| setCursorOwner(JSPType); |
| setRelativeOffset((fSourcePosition - start) + getCursorOwner().length()); |
| if (JSPType == EXPRESSION) { |
| // if an expression, add then length of the enclosing |
| // paren.. |
| setCursorInExpression(true); |
| setRelativeOffset(getRelativeOffset() + EXPRESSION_PREFIX.length()); |
| } |
| } |
| ITextRegion jspContent = null; |
| if (stRegion.getRegions() != null && stRegion.getRegions().size() > 1) |
| jspContent = stRegion.getRegions().get(1); |
| return (jspContent != null) ? stRegion.getFullText(jspContent) : stRegion.getFullText(); // don't |
| // unescape |
| // if |
| // it's |
| // not |
| // an |
| // XMLJSP |
| // tag |
| } |
| else if (stRegion.getType() == DOMJSPRegionContexts.JSP_CLOSE) { |
| // need to determine cursor owner so that the fCurosorPosition |
| // will be |
| // correct even if there is no region after the cursor in the JSP |
| // file |
| setCursorOwner(JSPType); |
| } |
| // iterate XMLCONTENT and CDATA regions |
| // loop fCurrentNode until you hit </jsp:scriptlet> (or other closing |
| // tag name) |
| while (getCurrentNode() != null && getCurrentNode().getType() != DOMRegionContext.XML_TAG_NAME && getCurrentNode().getType() != DOMJSPRegionContexts.JSP_CLOSE) // need to stop on the ending tag name... |
| { |
| start = getCurrentNode().getStartOffset(); |
| end = getCurrentNode().getEndOffset(); |
| bufferSize = buffer.length(); |
| CDATAOffset = unescapeRegion(getCurrentNode(), buffer); |
| fInCodeRegion = (start <= fSourcePosition && fSourcePosition <= end); |
| if (fInCodeRegion) { |
| setCursorOwner(JSPType); |
| // this offset is sort of complicated... |
| // it's composed of: |
| // 1. the length of the start of the current region up till |
| // where the cursor is |
| // 2. minus the number of characters lost in CDATA translation |
| // 3. plus the length of the escaped buffer before the current |
| // region, but |
| // is still within the jsp tag |
| setRelativeOffset((fSourcePosition - getCurrentNode().getStartOffset()) + getCursorOwner().length() - CDATAOffset + bufferSize); |
| if (JSPType == EXPRESSION) { |
| setCursorInExpression(true); |
| // if an expression, add then length of the enclosing |
| // paren.. |
| setRelativeOffset(getRelativeOffset() + EXPRESSION_PREFIX.length()); |
| } |
| } |
| if (getCurrentNode() != null) |
| advanceNextNode(); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * @param r |
| * the region to be unescaped (XMLContent, XML ENTITY |
| * REFERENCE, or CDATA) |
| * @param sb |
| * the stringbuffer to append the text to |
| * @return the number of characters removed in unescaping this text |
| */ |
| protected int unescapeRegion(ITextRegion r, StringBuffer sb) { |
| String s = ""; //$NON-NLS-1$ |
| int lengthBefore = 0, lengthAfter = 0, cdata_tags_length = 0; |
| if (r != null && (r.getType() == DOMRegionContext.XML_CONTENT || r.getType() == DOMRegionContext.XML_ENTITY_REFERENCE)) { |
| lengthBefore = (getCurrentNode() != r) ? getCurrentNode().getFullText(r).length() : getCurrentNode().getFullText().length(); |
| s = EscapedTextUtil.getUnescapedText(getCurrentNode(), r); |
| lengthAfter = s.length(); |
| sb.append(s); |
| } |
| else if (r != null && r.getType() == DOMRegionContext.XML_CDATA_TEXT) { |
| if (r instanceof ITextRegionContainer) // only interested in |
| // contents |
| { |
| // navigate to next region container (which should be a JSP |
| // region) |
| Iterator it = ((ITextRegionContainer) r).getRegions().iterator(); |
| ITextRegion temp = null; |
| while (it.hasNext()) { |
| temp = (ITextRegion) it.next(); |
| if (temp instanceof ITextRegionContainer || temp.getType() == DOMRegionContext.XML_CDATA_TEXT) { |
| sb.append(getCurrentNode().getFullText(temp)); |
| } |
| else if (temp.getType() == DOMRegionContext.XML_CDATA_OPEN || temp.getType() == DOMRegionContext.XML_CDATA_CLOSE) { |
| cdata_tags_length += temp.getLength(); |
| } |
| } |
| } |
| } |
| return (lengthBefore - lengthAfter + cdata_tags_length); |
| } |
| |
| // |
| // <jsp:useBean> |
| protected void translateUseBean(ITextRegionCollection container) { |
| ITextRegion r = null; |
| String attrName = null; |
| String attrValue = null; |
| String id = null; |
| ITextRegion idRegion = null; |
| String type = null; |
| ITextRegion typeRegion = null; |
| String className = null; |
| ITextRegion classnameRegion = null; |
| String beanName = null; |
| ITextRegion beanNameRegion = null; |
| |
| if (DOMRegionContext.XML_END_TAG_OPEN.equals(container.getFirstRegion().getType())) { |
| if (!fUseBeansStack.isEmpty()) { |
| fUseBeansStack.pop(); |
| appendToBuffer("}", fUserCode, false, fCurrentNode); //$NON-NLS-1$ |
| } |
| else { |
| // no useBean start tag being remembered |
| ITextRegionCollection extraEndRegion = container; |
| IJSPProblem missingStartTag = createJSPProblem(IJSPProblem.UseBeanStartTagMissing, IJSPProblem.F_PROBLEM_ID_LITERAL, NLS.bind(JSPCoreMessages.JSPTranslator_4,JSP11Namespace.ElementName.USEBEAN), extraEndRegion.getStartOffset(), extraEndRegion.getEndOffset()); |
| fTranslationProblems.add(missingStartTag); |
| } |
| return; |
| } |
| |
| Iterator regions = container.getRegions().iterator(); |
| while (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && (r.getType() != DOMRegionContext.XML_TAG_CLOSE || r.getType() != DOMRegionContext.XML_EMPTY_TAG_CLOSE)) { |
| attrName = attrValue = null; |
| if (r.getType().equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) { |
| attrName = container.getText(r).trim(); |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| if (regions.hasNext() && (r = (ITextRegion) regions.next()) != null && r.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| attrValue = StringUtils.stripQuotes(container.getText(r)); |
| } |
| // has equals, but no value? |
| } |
| // an attribute with no equals? |
| } |
| // (pa) might need different logic here if we wanna support more |
| if (attrName != null && attrValue != null) { |
| if (attrName.equals("id")) {//$NON-NLS-1$ |
| id = attrValue; |
| idRegion = r; |
| } |
| else if (attrName.equals("class")) {//$NON-NLS-1$ |
| className = attrValue; |
| classnameRegion = r; |
| } |
| else if (attrName.equals("type")) {//$NON-NLS-1$ |
| type = attrValue; |
| typeRegion = r; |
| } |
| else if (attrName.equals("beanName")) { //$NON-NLS-1$ |
| beanName = attrValue; |
| beanNameRegion = r; |
| } |
| } |
| } |
| |
| if (id != null) { |
| // The id is not a valid Java identifier |
| if (!isValidJavaIdentifier(id) && idRegion != null) { |
| Object problem = createJSPProblem(IJSPProblem.UseBeanInvalidID, IProblem.ParsingErrorInvalidToken, MessageFormat.format(JSPCoreMessages.JSPTranslator_0, new String[]{id}), container.getStartOffset(idRegion), container.getTextEndOffset(idRegion) - 1); |
| fTranslationProblems.add(problem); |
| } |
| // No Type information is provided |
| if (((type == null && className == null) || (type == null && beanName != null)) && idRegion != null) { |
| Object problem = createJSPProblem(IJSPProblem.UseBeanMissingTypeInfo, IProblem.UndefinedType, NLS.bind(JSPCoreMessages.JSPTranslator_3, new String[]{id}), container.getStartOffset(idRegion), container.getTextEndOffset(idRegion) - 1); |
| fTranslationProblems.add(problem); |
| } |
| // Cannot specify both a class and a beanName |
| if (className != null && beanName != null && beanNameRegion != null) { |
| ITextRegion nameRegion = container.getRegions().get(1); |
| Object problem = createJSPProblem(IJSPProblem.UseBeanAmbiguousType, IProblem.AmbiguousType, JSPCoreMessages.JSPTranslator_2, container.getStartOffset(nameRegion), container.getTextEndOffset(nameRegion) - 1); |
| fTranslationProblems.add(problem); |
| } |
| /* |
| * Only have a class or a beanName at this point, and potentially |
| * a type has id w/ type and/or classname/beanName |
| */ |
| // Type id = new Classname/Beanname(); |
| // or |
| // Type id = null; // if there is no classname or beanname |
| if ((type != null || className != null)) { |
| if (className != null) |
| className = decodeType(className); |
| |
| if (type == null) { |
| type = className; |
| typeRegion = classnameRegion; |
| } |
| else |
| type = decodeType(type); |
| |
| /* Now check the types (multiple of generics may be involved) */ |
| List errorTypeNames = new ArrayList(2); |
| if (!isTypeFound(type, errorTypeNames)) { |
| for (int i = 0; i < errorTypeNames.size(); i++) { |
| Object problem = createJSPProblem(IJSPProblem.F_PROBLEM_ID_LITERAL, IProblem.UndefinedType, MessageFormat.format(JSPCoreMessages.JSPTranslator_1, new String[]{errorTypeNames.get(i).toString()}), container.getStartOffset(typeRegion), container.getTextEndOffset(typeRegion) - 1); |
| fTranslationProblems.add(problem); |
| } |
| } |
| else { |
| String prefix = type + " " + id + " = "; //$NON-NLS-1$ //$NON-NLS-2$ |
| String suffix = "null;" + ENDL; //$NON-NLS-1$ |
| if (className != null) |
| suffix = "new " + className + "();" + ENDL; //$NON-NLS-1$ //$NON-NLS-2$ |
| else if (beanName != null) |
| suffix = "(" + type + ") java.beans.Beans.instantiate(getClass().getClassLoader(), \"" + beanName + "\");" + ENDL; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| appendToBuffer(prefix + suffix, fUserCode, true, fCurrentNode); |
| } |
| } |
| } |
| /* |
| * Add a brace and remember the start tag regardless of whether a |
| * variable was correctly created |
| */ |
| if (!DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(container.getLastRegion().getType())) { |
| fUseBeansStack.push(container); |
| appendToBuffer("{", fUserCode, false, fCurrentNode); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Decodes type strings for XML-style JSPs |
| * |
| * @param type the string to decode |
| * @return the decoded string |
| */ |
| private String decodeType(String type) { |
| final int length = type.length(); |
| final StringBuffer buffer = new StringBuffer(length); |
| for (int i = 0; i < length; i++) { |
| final char c = type.charAt(i); |
| if (c == '&') { |
| if (length > i + 3) { |
| final String code = type.substring(i + 1, i + 4); |
| final boolean isGt = "gt;".equals(code); //$NON-NLS-1$ |
| if (isGt || "lt;".equals(code)) { //$NON-NLS-1$ |
| i+=3; |
| buffer.append(isGt ? '>' : '<'); |
| continue; |
| } |
| } |
| } |
| buffer.append(c); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * @param type |
| * @return |
| */ |
| private boolean isTypeFound(String rawTypeValue, List errorTypeNames) { |
| // If the translation is being loaded from disk, the model and structured document may not have been intiailized yet |
| IFile file = getStructuredDocument() != null ? getFile() : (fSavedModelPath != null ? ResourcesPlugin.getWorkspace().getRoot().getFile(fSavedModelPath) : null); |
| if (file == null) |
| return true; |
| |
| IProject project = file.getProject(); |
| IJavaProject p = JavaCore.create(project); |
| if (p.exists()) { |
| List types = new ArrayList(); |
| if (rawTypeValue.indexOf('<') > 0) { |
| // JSR 14 : Generics are being used, parse them out |
| try { |
| StringTokenizer toker = new StringTokenizer(rawTypeValue); |
| String token = toker.nextToken("<,>/\""); //$NON-NLS-1$ |
| while (token != null) { |
| types.add(token); |
| token = toker.nextToken("<,>/\""); //$NON-NLS-1$ |
| } |
| } |
| catch (NoSuchElementException e) { |
| // StringTokenizer failure with unsupported syntax |
| return true; |
| } |
| } |
| else { |
| types.add(rawTypeValue); |
| } |
| |
| for (int i = 0; i < types.size(); i++) { |
| String typeName = types.get(i).toString(); |
| // remove any array suffixes |
| if (typeName.indexOf('[') > 0 && typeName.indexOf(']') > typeName.indexOf('[')) { |
| typeName = typeName.substring(0, typeName.indexOf('[')); |
| } |
| // remove any "extends" prefixes (JSR 14) |
| if (typeName.indexOf("extends") > 0) { //$NON-NLS-1$ |
| typeName = StringUtils.strip(typeName.substring(typeName.indexOf("extends"))); //$NON-NLS-1$ |
| } |
| |
| addNameToListIfTypeNotFound(p, typeName, errorTypeNames); |
| } |
| } |
| return errorTypeNames.isEmpty(); |
| } |
| |
| private void addNameToListIfTypeNotFound(IJavaProject p, String typeName, List collectedNamesNotFound) { |
| try { |
| if (typeName != null) { |
| IType type = p.findType(typeName); |
| if (type == null || !type.exists()) { |
| collectedNamesNotFound.add(typeName); |
| } |
| else { |
| IResource typeResource = type.getResource(); |
| if(typeResource != null) { |
| |
| } |
| } |
| } |
| } |
| catch (JavaModelException e) { |
| // Not a Java Project |
| } |
| } |
| |
| private boolean isValidJavaIdentifier(String id) { |
| char[] idChars = id.toCharArray(); |
| if (idChars.length < 1) |
| return false; |
| boolean isValid = Character.isJavaIdentifierStart(idChars[0]); |
| for (int i = 1; i < idChars.length; i++) { |
| isValid = isValid && Character.isJavaIdentifierPart(idChars[i]); |
| } |
| return isValid; |
| } |
| |
| final public int getCursorPosition() { |
| return fCursorPosition; |
| } |
| |
| protected boolean isCursorInExpression() { |
| return fCursorInExpression; |
| } |
| |
| protected void setCursorInExpression(boolean in) { |
| fCursorInExpression = in; |
| } |
| |
| final public void setSourceCursor(int i) { |
| fSourcePosition = i; |
| } |
| |
| final public int getSourcePosition() { |
| return fSourcePosition; |
| } |
| |
| final public TLDCMDocumentManager getTLDCMDocumentManager() { |
| return TaglibController.getTLDCMDocumentManager(fStructuredDocument); |
| } |
| |
| final public void setRelativeOffset(int relativeOffset) { |
| this.fRelativeOffset = relativeOffset; |
| } |
| |
| final public int getRelativeOffset() { |
| return fRelativeOffset; |
| } |
| |
| private void setCursorOwner(StringBuffer cursorOwner) { |
| this.fCursorOwner = cursorOwner; |
| } |
| |
| final public StringBuffer getCursorOwner() { |
| return fCursorOwner; |
| } |
| |
| private IStructuredDocumentRegion setCurrentNode(IStructuredDocumentRegion currentNode) { |
| return this.fCurrentNode = currentNode; |
| } |
| |
| final public IStructuredDocumentRegion getCurrentNode() { |
| return fCurrentNode; |
| } |
| |
| public IStructuredDocument getStructuredDocument() { |
| return fStructuredDocument; |
| } |
| |
| private IPath getModelPath() { |
| IPath path = null; |
| ITextFileBuffer buffer = FileBufferModelManager.getInstance().getBuffer(getStructuredDocument()); |
| if (buffer != null) { |
| path = buffer.getLocation(); |
| } |
| return path; |
| } |
| |
| private void translateCodas() { |
| fProcessIncludes = false; |
| IPath modelpath = getModelPath(); |
| if (modelpath != null) { |
| PropertyGroup[] propertyGroups = DeploymentDescriptorPropertyCache.getInstance().getPropertyGroups(modelpath); |
| for (int j = 0; j < propertyGroups.length; j++) { |
| IPath[] codas = propertyGroups[j].getIncludeCoda(); |
| for (int i = 0; i < codas.length; i++) { |
| if (!getIncludes().contains(codas[i].toString()) && !codas[i].equals(modelpath)) { |
| getIncludes().push(codas[i]); |
| JSPIncludeRegionHelper helper = new JSPIncludeRegionHelper(this, true); |
| helper.parse(codas[i].toString()); |
| getIncludes().pop(); |
| } |
| } |
| } |
| } |
| fProcessIncludes = true; |
| } |
| |
| private void translatePreludes() { |
| fProcessIncludes = false; |
| IPath modelpath = getModelPath(); |
| if (modelpath != null) { |
| PropertyGroup[] propertyGroups = DeploymentDescriptorPropertyCache.getInstance().getPropertyGroups(modelpath); |
| for (int j = 0; j < propertyGroups.length; j++) { |
| IPath[] preludes = propertyGroups[j].getIncludePrelude(); |
| for (int i = 0; i < preludes.length; i++) { |
| if (!getIncludes().contains(preludes[i].toString()) && !preludes[i].equals(modelpath)) { |
| getIncludes().push(preludes[i]); |
| JSPIncludeRegionHelper helper = new JSPIncludeRegionHelper(this, true); |
| helper.parse(preludes[i].toString()); |
| getIncludes().pop(); |
| } |
| } |
| } |
| } |
| fProcessIncludes = true; |
| } |
| |
| /** |
| * <p>Writes an empty expression to {@link #fUserCode} if previously |
| * found non translated code</p> |
| * <p>This should be done before appending any newly translated code.</p> |
| */ |
| private void writePlaceHolderForNonTranslatedCode() { |
| if(fFoundNonTranslatedCode) { |
| String text = ("{" + EXPRESSION_PREFIX + "\"\"" + EXPRESSION_SUFFIX + "}" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| " //markup"+ ENDL); //$NON-NLS-1$ |
| fUserCode.append(text); |
| fOffsetInUserCode += text.length(); |
| fFoundNonTranslatedCode = false; |
| } |
| } |
| |
| /** |
| * <p><b>NOTE: </b>If the implementation of this method is changed be sure to update |
| * {@link #readExternal(ObjectInput)} and {@link #serialVersionUID}</p> |
| * |
| * @see #readExternal(ObjectInput) |
| * @see #serialVersionUID |
| * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput) |
| */ |
| public void writeExternal(ObjectOutput out) throws IOException { |
| writeString(out, this.fClassHeader); |
| writeString(out, this.fClassname); |
| writeString(out, this.fSuperclass); |
| writeString(out, Boolean.toString(fIsDefaultSuperclass)); |
| writeString(out, this.fImplicitImports); |
| writeString(out, this.fServiceHeader); |
| writeBuffer(out, this.fUserImports); |
| out.writeInt(this.fSourcePosition); |
| out.writeInt(this.fRelativeOffset); |
| out.writeInt(this.fCursorPosition); |
| out.writeBoolean(this.fIsErrorPage); |
| out.writeBoolean(this.fCursorInExpression); |
| out.writeBoolean(this.fIsInASession); |
| writeBuffer(out, this.fUserCode); |
| writeBuffer(out, this.fUserELExpressions); |
| writeBuffer(out, this.fUserDeclarations); |
| writeBuffer(out, this.fCursorOwner); |
| out.writeBoolean(this.fInCodeRegion); |
| out.writeBoolean(this.fProcessIncludes); |
| out.writeInt(this.fOffsetInUserImports); |
| out.writeInt(this.fOffsetInUserDeclarations); |
| out.writeInt(this.fOffsetInUserCode); |
| |
| //write included paths |
| out.writeInt(this.fIncludedPaths.size()); |
| Iterator iter = this.fIncludedPaths.iterator(); |
| while(iter.hasNext()) { |
| writeString(out, (String)iter.next()); |
| } |
| |
| writeRanges(out, this.fImportRanges); |
| writeRanges(out, this.fCodeRanges); |
| writeRanges(out, this.fDeclarationRanges); |
| writeRanges(out, this.fUseBeanRanges); |
| writeRanges(out, this.fUserELRanges); |
| writeRanges(out, this.fIndirectRanges); |
| writeString(out, this.fELTranslatorID); |
| final IPath modelPath = getModelPath(); |
| writeString(out, modelPath != null && modelPath.segmentCount() > 1 ? modelPath.toString() : null ); |
| } |
| |
| /** |
| * <p><b>NOTE 1: </b>After reading in an externalized {@link JSPTranslator} the caller must |
| * manually call {@link #postReadExternalSetup(IStructuredModel)} to finish setting up |
| * the {@link JSPTranslator} for use.</p> |
| * |
| * <p><b>NOTE 2: </b>If the implementation of this method is changed be sure to update |
| * {@link #writeExternal(ObjectOutput)} and {@link #serialVersionUID}</p> |
| * |
| * @see #writeExternal(ObjectOutput) |
| * @see #serialVersionUID |
| * @see java.io.Externalizable#readExternal(java.io.ObjectInput) |
| */ |
| public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { |
| this.fClassHeader = readString(in); |
| this.fClassname = readString(in); |
| this.fSuperclass = readString(in); |
| this.fIsDefaultSuperclass = Boolean.parseBoolean(readString(in)); |
| this.fImplicitImports = readString(in); |
| this.fServiceHeader = readString(in); |
| this.fUserImports = new StringBuffer(readString(in)); |
| this.fSourcePosition = in.readInt(); |
| this.fRelativeOffset = in.readInt(); |
| this.fCursorPosition = in.readInt(); |
| this.fIsErrorPage = in.readBoolean(); |
| this.fCursorInExpression = in.readBoolean(); |
| this.fIsInASession = in.readBoolean(); |
| this.fUserCode = new StringBuffer(readString(in)); |
| this.fUserELExpressions = new StringBuffer(readString(in)); |
| this.fUserDeclarations = new StringBuffer(readString(in)); |
| this.fCursorOwner = new StringBuffer(readString(in)); |
| this.fInCodeRegion = in.readBoolean(); |
| this.fProcessIncludes = in.readBoolean(); |
| this.fOffsetInUserImports = in.readInt(); |
| this.fOffsetInUserDeclarations = in.readInt(); |
| this.fOffsetInUserCode = in.readInt(); |
| |
| //read included paths |
| int size = in.readInt(); |
| this.fIncludedPaths = new HashSet(size); |
| for(int i = 0; i < size; ++i) { |
| this.fIncludedPaths.add(readString(in)); |
| } |
| |
| this.fImportRanges = readRanges(in); |
| this.fCodeRanges = readRanges(in); |
| this.fDeclarationRanges = readRanges(in); |
| this.fUseBeanRanges = readRanges(in); |
| this.fUserELRanges = readRanges(in); |
| this.fIndirectRanges = readRanges(in); |
| this.fELTranslatorID = readString(in); |
| |
| final String path = readString(in); |
| if (path != null && path.trim().length() > 0) { |
| fSavedModelPath = new Path(path); |
| } |
| //build result |
| this.buildResult(false); |
| } |
| |
| /** |
| * <p>This does mandatory setup needed after a JSPTranslator is restored from |
| * a persisted file</p> |
| * |
| * @param jspModel {@link IStructuredModel} representing the JSP file that this |
| * is a translator for |
| */ |
| protected void postReadExternalSetup(IStructuredModel jspModel) { |
| this.fStructuredDocument = jspModel.getStructuredDocument(); |
| this.fJspTextBuffer = new StringBuffer(this.fStructuredDocument.get()); |
| if(jspModel instanceof IDOMModel) { |
| this.fStructuredModel = (IDOMModel)jspModel; |
| } |
| |
| // Do Post-Initialization of created problems if any |
| for (int i = 0; i < fTranslationProblems.size(); i++) { |
| if (fTranslationProblems.get(i) instanceof IJSPProblem) { |
| IJSPProblem problem = (IJSPProblem)fTranslationProblems.get(i); |
| if (problem.getSourceLineNumber() == -1) { |
| problem.setSourceLineNumber(fStructuredDocument.getLineOfOffset(problem.getSourceStart())); |
| } |
| } |
| } |
| } |
| |
| /** |
| * <p>Writes a string to an {@link ObjectOutput} stream</p> |
| * |
| * <p><b>NOTE: </b>If the implementation of this method is changed be sure to update |
| * {@link #readString(ObjectInput)} and {@link #serialVersionUID}</p> |
| * |
| * @param out {@link ObjectOutput} stream to write <code>s</code> too |
| * @param s {@link String} to write to <code>out</code> |
| * |
| * @throws IOException IO can throw exceptions |
| * |
| * @see #readString(ObjectInput) |
| */ |
| private static void writeString(ObjectOutput out, String s) throws IOException { |
| if(s != null) { |
| out.writeInt(s.length()); |
| out.writeChars(s); |
| } else { |
| out.writeInt(0); |
| } |
| } |
| |
| /** |
| * <p>Reads a {@link String} written by {@link #writeString(ObjectOutput, String)} from |
| * a {@link ObjectInput} stream.</p> |
| * |
| * <p><b>NOTE: </b>If the implementation of this method is changed be sure to update |
| * {@link #writeString(ObjectOutput, String)} and {@link #serialVersionUID}</p> |
| * |
| * @param in {@link ObjectInput} stream to read the {@link String} from |
| * @return {@link String} read from <code>in</code> |
| * |
| * @throws IOException IO Can throw exceptions |
| * |
| * @see #writeString(ObjectOutput, String) |
| */ |
| private static String readString(ObjectInput in) throws IOException { |
| int length = in.readInt(); |
| char charArray[] = new char[length]; |
| for(int i=0; i < length;i++){ |
| charArray[i] = in.readChar(); |
| } |
| return new String(charArray); |
| } |
| |
| /** |
| * <p>Writes a {@link StringBuffer} to an {@link ObjectOutput} stream</p> |
| * |
| * @param out {@link ObjectOutput} stream to write <code>s</code> too |
| * @param s {@link String} to write to <code>out</code> |
| * |
| * @throws IOException IO can throw exceptions |
| * |
| * @see #readString(ObjectInput) |
| */ |
| private static void writeBuffer(ObjectOutput out, StringBuffer buff) throws IOException { |
| if(buff != null && buff.length() > 0) { |
| writeString(out, buff.toString()); |
| } else { |
| writeString(out, null); |
| } |
| } |
| |
| /** |
| * <p>Writes a {@link HashMap} of {@link Position}s to an {@link ObjectOutput} stream</p> |
| * |
| * <p><b>NOTE: </b>If the implementation of this method is changed be sure to update |
| * {@link #readRanges(ObjectInput)} and {@link #serialVersionUID}</p> |
| * |
| * @param out {@link ObjectOutput} stream to write to |
| * @param ranges {@link HashMap} of {@link Position}s to write to <code>out</code> |
| * |
| * @throws IOException IO can throw exceptions |
| * |
| * @see #readRanges(ObjectInput) |
| */ |
| private static void writeRanges(ObjectOutput out, HashMap ranges) throws IOException { |
| //this is a strange hack because Position is not designed to be used as keys in a Map, see Position doc |
| HashMap temp = new HashMap(); |
| temp.putAll(ranges); |
| |
| Iterator iter = temp.keySet().iterator(); |
| out.writeInt(ranges.size()); |
| while(iter.hasNext()) { |
| Position javaPos = (Position)iter.next(); |
| Position jspPos = (Position)temp.get(javaPos); |
| out.writeInt(javaPos.offset); |
| out.writeInt(javaPos.length); |
| out.writeBoolean(javaPos.isDeleted); |
| |
| if(jspPos != null) { |
| out.writeInt(jspPos.offset); |
| out.writeInt(jspPos.length); |
| out.writeBoolean(jspPos.isDeleted); |
| } else { |
| out.writeInt(-1); |
| out.writeInt(-1); |
| } |
| } |
| } |
| |
| /** |
| * <p>Reads a {@link HashMap} of {@link Position}s from an {@link ObjectInput} stream that was written by |
| * {@link #writeRanges(ObjectOutput, HashMap)}</p> |
| * |
| * <p><b>NOTE: </b>If the implementation of this method is changed be sure to update |
| * {@link #writeRanges(ObjectOutput, HashMap)} and {@link #serialVersionUID}</p> |
| * |
| * @param in {@link ObjectInput} stream to read the {@link HashMap} of {@link Position}s from |
| * @return {@link HashMap} of {@link Position}s read from <code>in</code> |
| * |
| * @throws IOException IO can throw exceptions |
| * |
| * @see #writeRanges(ObjectOutput, HashMap) |
| */ |
| private static HashMap readRanges(ObjectInput in) throws IOException { |
| int size = in.readInt(); |
| HashMap ranges = new HashMap(size); |
| for(int i = 0; i < size; ++i) { |
| Position javaPos = new Position(in.readInt(), in.readInt()); |
| if(in.readBoolean()) { |
| javaPos.delete(); |
| } |
| |
| //if the jspPos was null for some reason then -1 was written for length and offset |
| Position jspPos = null; |
| int jspPosOffset = in.readInt(); |
| int jspPosLength = in.readInt(); |
| if(jspPosOffset != -1 && jspPosLength != -1) { |
| jspPos = new Position(jspPosOffset, jspPosLength); |
| } |
| |
| //only read a boolean if the jspPos was not null |
| if(jspPos != null && in.readBoolean()) { |
| jspPos.delete(); |
| } |
| |
| ranges.put(javaPos, jspPos); |
| } |
| |
| return ranges; |
| } |
| |
| /** |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object obj) { |
| boolean equal = false; |
| if(obj instanceof JSPTranslator) { |
| JSPTranslator other = (JSPTranslator)obj; |
| equal = this.fClassHeader.equals(other.fClassHeader) && |
| this.fClassname.equals(other.fClassname) && |
| this.fSuperclass.equals(other.fSuperclass) && |
| this.fImplicitImports.equals(other.fImplicitImports) && |
| this.fServiceHeader.equals(other.fServiceHeader) && |
| buffersEqual(this.fUserImports, other.fUserImports) && |
| this.fSourcePosition == other.fSourcePosition && |
| this.fRelativeOffset == other.fRelativeOffset && |
| this.fCursorPosition == other.fCursorPosition && |
| this.fIsErrorPage == other.fIsErrorPage && |
| this.fCursorInExpression == other.fCursorInExpression && |
| this.fIsInASession == other.fIsInASession && |
| buffersEqual(this.fUserCode, other.fUserCode) && |
| buffersEqual(this.fUserELExpressions, other.fUserELExpressions) && |
| buffersEqual(this.fUserDeclarations, other.fUserDeclarations) && |
| buffersEqual(this.fCursorOwner, other.fCursorOwner) && |
| this.fInCodeRegion == other.fInCodeRegion && |
| this.fProcessIncludes == other.fProcessIncludes && |
| this.fOffsetInUserImports == other.fOffsetInUserImports && |
| this.fOffsetInUserDeclarations == other.fOffsetInUserDeclarations && |
| this.fOffsetInUserCode == other.fOffsetInUserCode && |
| rangesEqual(this.fImportRanges, other.fImportRanges) && |
| rangesEqual(this.fCodeRanges, other.fCodeRanges) && |
| rangesEqual(this.fDeclarationRanges, other.fDeclarationRanges) && |
| rangesEqual(this.fUseBeanRanges, other.fUseBeanRanges) && |
| rangesEqual(this.fUserELRanges, other.fUserELRanges) && |
| rangesEqual(this.fIndirectRanges, other.fIndirectRanges) && |
| ( |
| (this.fELTranslatorID != null && other.fELTranslatorID != null) || |
| (this.fELTranslatorID == null && other.fELTranslatorID != null && other.fELTranslatorID.length() == 0) || |
| (this.fELTranslatorID != null && this.fELTranslatorID.length() == 0 && other.fELTranslatorID == null) || |
| (this.fELTranslatorID.equals(other.fELTranslator)) |
| ); |
| } |
| return equal; |
| } |
| |
| /** |
| * <p><code>null</code> is considered equivlent to an empty buffer</p> |
| * |
| * @param buff1 can be <code>null</code> |
| * @param buff2 can be <code>null</code> |
| * @return <code>true</code> if the two given buffers are equal, <codee>false</code> otherwise |
| */ |
| private static boolean buffersEqual(StringBuffer buff1, StringBuffer buff2) { |
| return (buff1 == null && buff2 == null) || |
| (buff1 != null && buff2!= null && buff1.toString().equals(buff2.toString())) || |
| (buff1 == null && buff2 != null && buff2.length() == 0) || |
| (buff1 != null && buff1.length() == 0 && buff2 == null); |
| } |
| |
| /** |
| * @param ranges1 |
| * @param ranges2 |
| * @return <code>true</code> if the two maps of ranges contains the same key/value pares, |
| * <code>false</code> otherwise |
| */ |
| private static boolean rangesEqual(HashMap ranges1, HashMap ranges2) { |
| //this is a strange hack because Position is not designed to be used as keys in a Map, see Position doc |
| HashMap temp = new HashMap(); |
| temp.putAll(ranges1); |
| ranges1 = temp; |
| |
| boolean equal = false; |
| if(ranges1 != null && ranges2 != null) { |
| equal = true; |
| Iterator ranges1Keys = ranges1.keySet().iterator(); |
| while(ranges1Keys.hasNext() && equal) { |
| Position key = (Position)ranges1Keys.next(); |
| Position ranges1Value = (Position)ranges1.get(key); |
| Position ranges2Value = (Position)ranges2.get(key); |
| equal = ranges1Value.equals(ranges2Value); |
| } |
| } |
| |
| return equal; |
| } |
| } |