/******************************************************************************* * Copyright (c) 2015 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.batch.internal.core.el; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.core.resources.IFile; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.ui.StructuredTextEditor; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.jboss.tools.batch.core.BatchConstants; import org.jboss.tools.batch.core.BatchCorePlugin; import org.jboss.tools.batch.internal.core.impl.BatchUtil; import org.jboss.tools.common.el.core.ca.AbstractELCompletionEngine; import org.jboss.tools.common.el.core.ca.MessagesELTextProposal; import org.jboss.tools.common.el.core.model.ELArgumentInvocation; import org.jboss.tools.common.el.core.model.ELExpression; import org.jboss.tools.common.el.core.model.ELInvocationExpression; import org.jboss.tools.common.el.core.model.ELObjectType; import org.jboss.tools.common.el.core.model.ELPropertyInvocation; import org.jboss.tools.common.el.core.parser.ELParserFactory; import org.jboss.tools.common.el.core.parser.ELParserUtil; import org.jboss.tools.common.el.core.resolver.ELContext; import org.jboss.tools.common.el.core.resolver.ELResolution; import org.jboss.tools.common.el.core.resolver.ELResolutionImpl; import org.jboss.tools.common.el.core.resolver.ELResolverExtension; import org.jboss.tools.common.el.core.resolver.ELSegmentImpl; import org.jboss.tools.common.el.core.resolver.IOpenableReference; import org.jboss.tools.common.el.core.resolver.IRelevanceCheck; import org.jboss.tools.common.el.core.resolver.IVariable; import org.jboss.tools.common.el.core.resolver.TypeInfoCollector.MemberInfo; import org.jboss.tools.common.text.TextProposal; import org.jboss.tools.common.util.StringUtil; import org.jboss.tools.common.validation.SkipValidation; import org.jboss.tools.common.xml.XMLUtilities; import org.jboss.tools.jst.web.kb.IXmlContext; import org.jboss.tools.jst.web.kb.PageContextFactory; import org.jboss.tools.jst.web.kb.WebKbPlugin; import org.jboss.tools.jst.web.kb.internal.XmlContextImpl; import org.jboss.tools.jst.web.kb.taglib.INameSpace; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * * @author Viacheslav Kabanovich * */ @SkipValidation public class JobPropertiesELCompletionEngine extends AbstractELCompletionEngine<IVariable> implements ELResolverExtension { public static final String PROPERTY_IMAGE_NAME = "property.png"; //$NON-NLS-1$ private static final ImageDescriptor JOB_PROPERTIES_PROPOSAL_IMAGE = ImageDescriptor.createFromFile(JobPropertiesELCompletionEngine.class, PROPERTY_IMAGE_NAME); @Override public ImageDescriptor getELProposalImageForMember(MemberInfo memberInfo) { return JOB_PROPERTIES_PROPOSAL_IMAGE; } public JobPropertiesELCompletionEngine() {} @Override public boolean isRelevant(ELContext context) { if(context instanceof IXmlContext) { Map<String, List<INameSpace>> namespaces = ((IXmlContext)context).getRootNameSpaces(); if(namespaces.containsKey(BatchConstants.JAVAEE_NAMESPACE)) { for (INameSpace n: namespaces.get(BatchConstants.JAVAEE_NAMESPACE)) { if(BatchConstants.TAG_JOB.equals(n.getRoot())) { return true; } } } } return false; } @Override public ELParserFactory getParserFactory() { return ELParserUtil.getDefaultFactory(); } @Override protected void log(Exception e) { BatchCorePlugin.pluginLog().logError(e); } @Override public List<TextProposal> getProposals(ELContext context, String el, int offset) { if(!isRelevant(context)) { return null; } currentOffset = offset; List<TextProposal> proposals = null; try { proposals = getCompletions(context.getResource(), el.subSequence(0, el.length()), false); } catch (StringIndexOutOfBoundsException e) { log(e); } catch (BadLocationException e) { log(e); } return proposals; } int currentOffset = 0; @Override public ELResolution resolve(ELContext context, ELExpression operand, int offset) { currentOffset = offset; ELResolutionImpl resolution = resolveELOperand(operand, context, true); if(resolution != null) resolution.setContext(context); return resolution; } public ELResolutionImpl resolveELOperand(ELExpression operand, ELContext context, boolean returnEqualedVariablesOnly) { try { return resolveELOperand(context.getResource(), operand, returnEqualedVariablesOnly); } catch (StringIndexOutOfBoundsException e) { log(e); } catch (BadLocationException e) { log(e); } return null; } private List<TextProposal> getCompletions(IFile file, CharSequence prefix, boolean returnEqualedVariablesOnly) throws BadLocationException, StringIndexOutOfBoundsException { List<TextProposal> completions = new ArrayList<TextProposal>(); ELResolutionImpl status = resolveELOperand(file, parseOperand("" + prefix), returnEqualedVariablesOnly); //$NON-NLS-1$ if(status != null) { completions.addAll(status.getProposals()); } return completions; } static List<?> EMPTY_OBJECTS = Collections.unmodifiableList(new ArrayList()); protected ELResolutionImpl resolveELOperand(IFile file, ELExpression operand, boolean returnEqualedVariablesOnly) throws BadLocationException, StringIndexOutOfBoundsException { if(!(operand instanceof ELInvocationExpression) || file == null) { return null; } ELInvocationExpression expr = (ELInvocationExpression)operand; boolean isIncomplete = expr.getType() == ELObjectType.EL_PROPERTY_INVOCATION && ((ELPropertyInvocation)expr).getName() == null; boolean isArgument = expr.getType() == ELObjectType.EL_ARGUMENT_INVOCATION; ELResolutionImpl resolution = new ELResolutionImpl(expr); ELInvocationExpression left = expr; List<IVariable> resolvedVariables = EMPTY_VARIABLES_LIST; if (expr.getLeft() != null && isArgument) { left = expr.getLeft(); resolvedVariables = resolveVariables(file, left, false, true); // is Final and equal names are because of // we have no more to resolve the parts of expression, // but we have to resolve arguments of probably a message component if (resolvedVariables != null && !resolvedVariables.isEmpty()) { resolution.setLastResolvedToken(left); ELSegmentImpl segment = new ELSegmentImpl(left.getFirstToken()); segment.setResolved(true); for (IVariable variable : resolvedVariables) { segment.getVariables().add(variable); } resolution.addSegment(segment); } } else if (expr.getLeft() == null && isIncomplete) { resolvedVariables = resolveVariables(file, expr, true, returnEqualedVariablesOnly); } else { while(left != null) { List<IVariable> resolvedVars = resolveVariables(file, left, left == expr, returnEqualedVariablesOnly); if (resolvedVars != null && !resolvedVars.isEmpty()) { resolvedVariables = resolvedVars; resolution.setLastResolvedToken(left); ELSegmentImpl segment = new JobPropertyELSegmentImpl(left.getFirstToken()); segment.setResolved(true); for (IVariable variable : resolvedVars) { segment.getVariables().add(variable); } resolution.addSegment(segment); break; } left = (ELInvocationExpression)left.getLeft(); } } if (resolution.getLastResolvedToken() == null && !returnEqualedVariablesOnly && expr != null && isIncomplete) { resolvedVariables = resolveVariables(file, expr, true, returnEqualedVariablesOnly); Set<TextProposal> proposals = new TreeSet<TextProposal>(TextProposal.KB_PROPOSAL_ORDER); if (left != null) { ELSegmentImpl segment = new JobPropertyELSegmentImpl(left.getFirstToken()); segment.setResolved(false); resolution.addSegment(segment); for (IVariable var : resolvedVariables) { String varName = var.getName(); if(varName.startsWith(operand.getText())) { MessagesELTextProposal proposal = new MessagesELTextProposal(); proposal.setReplacementString(varName.substring(operand.getLength())); proposal.setImageDescriptor(getELProposalImageForMember(null)); List<?> objects = EMPTY_OBJECTS; proposal.setBaseName(""); proposal.setObjects(objects); proposals.add(proposal); } } resolution.setProposals(proposals); segment.setResolved(!proposals.isEmpty()); } return resolution; } // Here we have a list of vars for some part of expression // OK. we'll proceed with members of these vars if (resolution.getLastResolvedToken() == operand) { // First segment is the last one Set<TextProposal> proposals = new TreeSet<TextProposal>(TextProposal.KB_PROPOSAL_ORDER); for (IVariable var : resolvedVariables) { String varName = var.getName(); if(operand.getLength()<=varName.length()) { MessagesELTextProposal proposal = new MessagesELTextProposal(); proposal.setReplacementString(varName.substring(operand.getLength())); proposal.setLabel(varName); proposal.setImageDescriptor(getELProposalImageForMember(null)); List<?> objects = EMPTY_OBJECTS; proposal.setBaseName(""); proposal.setObjects(objects); proposals.add(proposal); } else if(returnEqualedVariablesOnly) { TextProposal proposal = new TextProposal(); proposal.setReplacementString(varName); proposal.setLabel(varName); proposal.setImageDescriptor(getELProposalImageForMember(null)); proposals.add(proposal); } resolution.getLastSegment().getVariables().add(var); } resolution.setLastResolvedToken(expr); resolution.setProposals(proposals); return resolution; } //process segments one by one if(left != null) { while(left != expr) { left = (ELInvocationExpression)left.getParent(); if (left != expr) { // inside expression ELSegmentImpl segment = new ELSegmentImpl(left.getLastToken()); segment.setResolved(true); resolution.addSegment(segment); resolution.setLastResolvedToken(left); return resolution; } else { // Last segment resolveLastSegment(file, (ELInvocationExpression)operand, resolvedVariables, resolution, returnEqualedVariablesOnly); break; } } } else { ELSegmentImpl segment = new ELSegmentImpl(expr.getFirstToken()); resolution.addSegment(segment); } return resolution; } protected List<IVariable> resolveVariables(IFile file, ELInvocationExpression expr, boolean isFinal, boolean onlyEqualNames) { List<IVariable> result = EMPTY_VARIABLES_LIST; if(expr.getLeft() != null) return result; String varName = expr.toString(); for (IVariable v: getAllVariables()) { if(!isFinal || onlyEqualNames) { if(!v.getName().equals(varName)) { continue; } } if(!v.getName().startsWith(varName)) { continue; } if(result == EMPTY_VARIABLES_LIST) { result = new ArrayList<IVariable>(); } result.add(v); } return result; } protected IVariable[] getAllVariables() { return new IVariable[]{JOB_PROPERTIES, JOB_PARAMETERS, SYSTEM_PROPERTIES, PARTITION_PLAN}; } protected void resolveLastSegment(IFile file, ELInvocationExpression expr, List<IVariable> members, ELResolutionImpl resolution, boolean returnEqualedVariablesOnly) { ELSegmentImpl segment = new ELSegmentImpl(expr.getFirstToken()); if(expr instanceof ELPropertyInvocation) { segment = new JobPropertyELSegmentImpl(((ELPropertyInvocation)expr).getName()); processJobPropertySegment(file, expr, (JobPropertyELSegmentImpl)segment, members); } else if (expr instanceof ELArgumentInvocation) { segment = new JobPropertyELSegmentImpl(((ELArgumentInvocation)expr).getArgument().getOpenArgumentToken().getNextToken()); processJobPropertySegment(file, expr, (JobPropertyELSegmentImpl)segment, members); } if(segment.getToken()!=null) { resolution.addSegment(segment); } addTextProposals(file, expr, members, resolution, segment, returnEqualedVariablesOnly); if (resolution.isResolved()){ resolution.setLastResolvedToken(expr); } } protected void addTextProposals(IFile file, ELInvocationExpression expr, List<IVariable> members, ELResolutionImpl resolution, ELSegmentImpl segment, boolean returnEqualedVariablesOnly) { Set<TextProposal> kbProposals = new TreeSet<TextProposal>(TextProposal.KB_PROPOSAL_ORDER); resolution.setProposals(kbProposals); if (expr.getType() == ELObjectType.EL_PROPERTY_INVOCATION && ((ELPropertyInvocation)expr).getName() == null) { //Batch job does not support property invocation with dot } else if(expr.getType() != ELObjectType.EL_ARGUMENT_INVOCATION) { //Batch job substitution does not property invocation with dot } else if(expr.getType() == ELObjectType.EL_ARGUMENT_INVOCATION) { String filter = expr.getMemberName() == null ? "" : expr.getMemberName(); filter = StringUtil.trimQuotes(filter); for (IVariable mbr : members) { if(mbr != JOB_PROPERTIES) { continue; } List<OpenableReference> properties = getProperties(file, currentOffset); for (OpenableReference p : properties) { String key = p.getValue(); if(returnEqualedVariablesOnly) { // This is used for validation. if (key.equals(filter)) { MessagesELTextProposal kbProposal = createProposal(mbr, key); kbProposals.add(kbProposal); break; } } else if (key.startsWith(filter)) { // This is used for CA. MessagesELTextProposal kbProposal = createProposal(mbr, key); String existingString = expr.getMemberName() == null ? "" : expr.getMemberName(); // Because we're in argument invocation we should fix the proposal by surrounding it with quotes as needed String replacement = kbProposal.getReplacementString(); String label = kbProposal.getLabel(); if (!replacement.startsWith("'")) { replacement = '\'' + key + '\''; label = "['" + key + "']"; } replacement = replacement.substring(existingString.length()); kbProposal.setReplacementString(replacement); kbProposal.setLabel(label); kbProposals.add(kbProposal); } } } } segment.setResolved(!kbProposals.isEmpty()); } private void processJobPropertySegment(IFile file, ELInvocationExpression expr, JobPropertyELSegmentImpl segment, List<IVariable> variables){ if(segment.getToken() == null) return; for(IVariable variable : variables){ if(variable != JOB_PROPERTIES) { continue; } if(expr.getFirstToken().getText().equals(variable.getName())){ List<OpenableReference> properties = getProperties(file, currentOffset); List<OpenableReference> result = new ArrayList<OpenableReference>(); for (OpenableReference r: properties) { if(r.getValue().equals(StringUtil.trimQuotes(segment.getToken().getText()))) { result.add(r); } } segment.setResource(file); segment.setAttrs(result); } } } private List<OpenableReference> getProperties(final IFile file, int offset) { final List<OpenableReference> result = new ArrayList<OpenableReference>(); ELContext c = PageContextFactory.createPageContext(file); if(!(c instanceof XmlContextImpl)) return result; XmlContextImpl context = (XmlContextImpl)c; final IDocument idocument = context.getDocument(); if(idocument == null) return result; BatchUtil.scanXMLFile(file, new BatchUtil.DocumentScanner() { @Override public void scanDocument(Document document) { Node n = findNodeForOffset((IDOMDocument)document, currentOffset); if(n instanceof Attr) { fillProperties(idocument, file, result, (Element)n.getParentNode()); } else if(n instanceof Element) { fillProperties(idocument, file, result, (Element)n); } } }); return result; } void fillProperties(IDocument document, IFile file, List<OpenableReference> result, Element element) { Attr exclude = null; if(element.getNodeName().equals(BatchConstants.TAG_PROPERTY)) { exclude = element.getAttributeNode(BatchConstants.ATTR_NAME); element = (Element)element.getParentNode(); } if(element.getNodeName().equals(BatchConstants.TAG_PROPERTIES)) { element = (Element)element.getParentNode(); } while(element != null) { Element psn = element; if(!element.getNodeName().equals(BatchConstants.TAG_PROPERTIES)) { psn = XMLUtilities.getUniqueChild(element, BatchConstants.TAG_PROPERTIES); } if(psn != null) { Element[] ps = XMLUtilities.getChildren(psn, BatchConstants.TAG_PROPERTY); for (Element p: ps) { Attr a = p.getAttributeNode(BatchConstants.ATTR_NAME); if(a != exclude && a instanceof IDOMAttr) { result.add(new OpenableReference(document, file, (IDOMAttr)a)); } } } if(element.getParentNode() instanceof Element) { element = (Element)element.getParentNode(); } else { element = null; } } } static public Node findNodeForOffset(IDOMNode node, int offset) { IndexedRegion region = node.getModel().getIndexedRegion(offset); if(region instanceof Node){ return (Node)region; } return null; } private MessagesELTextProposal createProposal(IVariable mbr, String proposal) { MessagesELTextProposal kbProposal = new MessagesELTextProposal(); if (proposal.indexOf('.') != -1) { kbProposal.setReplacementString('\'' + proposal + '\''); kbProposal.setLabel("['" + proposal + "']"); } else { kbProposal.setReplacementString(proposal); kbProposal.setLabel(proposal); } kbProposal.setAlternateMatch(proposal); kbProposal.setImageDescriptor(getELProposalImageForMember(null)); List<?> objects = EMPTY_OBJECTS; //TODO do another implementation kbProposal.setBaseName(""); kbProposal.setPropertyName(proposal); kbProposal.setObjects(objects); return kbProposal; } static IVariable JOB_PROPERTIES = new Variable(BatchConstants.JOB_PROPERTIES_OPERATOR); static IVariable JOB_PARAMETERS = new Variable(BatchConstants.JOB_PARAMETERS_OPERATOR); static IVariable SYSTEM_PROPERTIES = new Variable(BatchConstants.SYSTEM_PROPERTIES_OPERATOR); static IVariable PARTITION_PLAN = new Variable(BatchConstants.PARTITION_PLAN_OPERATOR); static class Variable implements IVariable { String name; Variable(String name) { this.name = name; } @Override public String getName() { return name; } } public static final String GO_TO_PROPERTY_AT = "Property {0} at {1}:{2}"; static class OpenableReference implements IOpenableReference { IFile file; int start; int length; String value; String label; OpenableReference(IDocument document, IFile file, IDOMAttr attr) { this.file = file; this.start = attr.getValueRegionStartOffset(); this.length = attr.getValueRegionText().length(); value = attr.getValue(); int line = 0; int pos = 0; try { line = document.getLineOfOffset(start); pos = start - document.getLineOffset(line); } catch (BadLocationException e) { BatchCorePlugin.pluginLog().logError(e); } label = NLS.bind(GO_TO_PROPERTY_AT, new Object[]{value, "" + line, "" + pos}); } @Override public boolean open() { try { IEditorPart part = IDE.openEditor(WebKbPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage(), file); StructuredTextEditor text = (StructuredTextEditor)part.getAdapter(ITextEditor.class); text.selectAndReveal(start, length); } catch (PartInitException e) { BatchCorePlugin.pluginLog().logError(e); } return false; } @Override public String getLabel() { return label; } public String getValue() { return value; } @Override public Image getImage() { return null; } } @Override protected MemberInfo getMemberInfoByVariable(IVariable var, ELContext context, boolean onlyEqualNames, int offset) { return null; } @Override public List<IVariable> resolveVariables(IFile file, ELContext context, ELInvocationExpression expr, boolean isFinal, boolean onlyEqualNames, int offset) { return null; } @Override protected boolean isStaticMethodsCollectingEnabled() { return false; } @Override public IRelevanceCheck createRelevanceCheck(IJavaElement element) { return IRRELEVANT; } }