/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.editor.scriptdoc.parsing.reader; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import com.aptana.ide.core.IdeLog; import com.aptana.ide.core.StringUtils; import com.aptana.ide.editor.js.JSPlugin; import com.aptana.ide.editor.scriptdoc.parsing.AliasEntry; import com.aptana.ide.editor.scriptdoc.parsing.FunctionDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.ProjectDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.PropertyDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.ScriptDoc; import com.aptana.ide.editor.scriptdoc.parsing.TypedDescription; import com.aptana.ide.io.StreamUtils; import com.aptana.ide.metadata.UserAgent; import com.aptana.sax.NamespaceSniffer; import com.aptana.sax.SchemaBuilder; import com.aptana.sax.SchemaInitializationException; import com.aptana.sax.ValidatingReader; /** * @author Kevin Lindsey */ public class ScriptDocReader extends ValidatingReader { private ScriptDoc _documentation; private ProjectDocumentation _currentProject; private PropertyDocumentation _currentProperty; private TypedDescription _currentParameter; private FunctionDocumentation _currentClass; private FunctionDocumentation _currentMethod; private TypedDescription _currentValue; private boolean _inReturnType; private int _returnTypeCount; private boolean _currentException = false; private List<FunctionDocumentation> _functions; private List<PropertyDocumentation> _properties; private List<AliasEntry> _aliases; private List<String> _references; private boolean _bufferText; private String _textBuffer; private boolean _parsingCtors; private UserAgent _currentUserAgent; private boolean _sourceInstanceProperties; /** * Create a new instance of CoreLoader * * @throws ScriptDocInitializationException */ public ScriptDocReader() throws ScriptDocInitializationException { // get schema for our documentation XML format InputStream schemaStream = ScriptDocReader.class.getResourceAsStream("/com/aptana/ide/editor/scriptdoc/resources/DocumentationSchema.xml"); //$NON-NLS-1$ try { // create the schema this._schema = SchemaBuilder.fromXML(schemaStream, this); } catch (SchemaInitializationException e) { String msg = Messages.ScriptDocReader_SchemaError; ScriptDocInitializationException ie = new ScriptDocInitializationException(msg, e); throw ie; } finally { // close the input stream try { schemaStream.close(); } catch (IOException e) { String msg = Messages.ScriptDocReader_IOError; ScriptDocInitializationException ie = new ScriptDocInitializationException(msg, e); throw ie; } } this._functions = new ArrayList<FunctionDocumentation>(); this._properties = new ArrayList<PropertyDocumentation>(); this._aliases = new ArrayList<AliasEntry>(); this._references = new ArrayList<String>(); } /** * Process character data * * @param buffer * @param offset * @param length */ public void characters(char[] buffer, int offset, int length) { if (this._bufferText) { this._textBuffer += new String(buffer, offset, length); } } /** * start processing an alias element * * @param ns * @param name * @param qname * @param attributes */ public void enterAlias(String ns, String name, String qname, Attributes attributes) { String aliasName = attributes.getValue("name"); //$NON-NLS-1$ String aliasType = attributes.getValue("type"); //$NON-NLS-1$ if (aliasName != null && aliasName.length() > 0 && aliasType != null && aliasType.length() > 0) { this._aliases.add(new AliasEntry(aliasName, aliasType)); } } /** * start processing a browser element * * @param ns * @param name * @param qname * @param attributes */ public void enterBrowser(String ns, String name, String qname, Attributes attributes) { // create a new item documentation object UserAgent field = new UserAgent(); String platform = attributes.getValue("platform"); //$NON-NLS-1$ field.setPlatform(platform); String version = attributes.getValue("version"); //$NON-NLS-1$ if (version != null) { field.setVersion(version); } String os = attributes.getValue("os"); //$NON-NLS-1$ if (os != null) { field.setOs(os); } String osVersion = attributes.getValue("osVersion"); //$NON-NLS-1$ if (osVersion != null) { field.setOsVersion(osVersion); } this._currentUserAgent = field; } /** * start processing a class element * * @param ns * @param name * @param qname * @param attributes */ public void enterClass(String ns, String name, String qname, Attributes attributes) { // create a new class documentation object FunctionDocumentation classDoc = new FunctionDocumentation(); // grab and set property values String type = attributes.getValue("type"); //$NON-NLS-1$ String superclass = attributes.getValue("superclass"); //$NON-NLS-1$ // set type classDoc.setName(type); // set optional superclass if (superclass != null && superclass.length() > 0) { String[] types = superclass.split("\\s+"); //$NON-NLS-1$ for (String superType : types) { if (type != null && type.length() > 0) { classDoc.getExtends().addType(superType); } } } // set current class this._currentClass = classDoc; this._functions.add(this._currentClass); } /** * enterConstructors * * @param ns * @param name * @param qname * @param attributes */ public void enterConstructors(String ns, String name, String qname, Attributes attributes) { this._parsingCtors = true; } /** * start processing an exception element * * @param ns * @param name * @param qname * @param attributes */ public void enterException(String ns, String name, String qname, Attributes attributes) { // set current exception this._currentException = true; } /** * Start processing a method element * * @param ns * @param name * @param qname * @param attributes */ public void enterMethod(String ns, String name, String qname, Attributes attributes) { // create a new method documentation object FunctionDocumentation methodDoc = new FunctionDocumentation(); if (_parsingCtors) { methodDoc.setExtends(_currentClass.getExtends()); methodDoc.setIsConstructor(true); // for this xml format isCtor is always one or the other, user code may vary methodDoc.setIsMethod(false); } else { methodDoc.setIsConstructor(false); methodDoc.setIsMethod(true); } // determine and set method name String mname = attributes.getValue("name"); //$NON-NLS-1$ if (mname == null) { methodDoc.setName("#ctor"); //$NON-NLS-1$ } else { methodDoc.setName(mname); } String scope = attributes.getValue("scope"); //$NON-NLS-1$ if (scope != null && scope.equals("instance")) //$NON-NLS-1$ { methodDoc.setIsInstance(true); } else if (scope.equals("invocation")) //$NON-NLS-1$ { methodDoc.setIsInvocationOnly(true); } String visibility = attributes.getValue("visibility"); //$NON-NLS-1$ if (visibility != null && visibility.equals("internal")) //$NON-NLS-1$ { methodDoc.setIsInternal(true); } methodDoc.getMemberOf().addType(_currentClass.getName()); this._currentMethod = methodDoc; // reset return type count this._returnTypeCount = 0; } /** * enterMixin * * @param ns * @param name * @param qname * @param attributes */ public void enterMixin(String ns, String name, String qname, Attributes attributes) { boolean targetInstanceProperties; String scope = attributes.getValue("scope"); //$NON-NLS-1$ String type = attributes.getValue("type"); //$NON-NLS-1$ targetInstanceProperties = ("instance".equals(scope)); //$NON-NLS-1$ this._currentClass.addMixin(type, this._sourceInstanceProperties, targetInstanceProperties); } /** * enterMixins * * @param ns * @param name * @param qname * @param attributes */ public void enterMixins(String ns, String name, String qname, Attributes attributes) { String scope = attributes.getValue("scope"); //$NON-NLS-1$ this._sourceInstanceProperties = ("instance".equals(scope)); //$NON-NLS-1$ } /** * Start processing a parameter element * * @param ns * @param name * @param qname * @param attributes */ public void enterParameter(String ns, String name, String qname, Attributes attributes) { // create a new parameter documentation object TypedDescription parameter = new TypedDescription(); // grab and set properties String pname = attributes.getValue("name"); //$NON-NLS-1$ String type = attributes.getValue("type"); //$NON-NLS-1$ parameter.setName(pname); parameter.addType(type); // store parameter this._currentParameter = parameter; } /** * Start processing a property element * * @param ns * @param name * @param qname * @param attributes */ public void enterProperty(String ns, String name, String qname, Attributes attributes) { // create a new property documentation object PropertyDocumentation propertyDoc = new PropertyDocumentation(); // grab and set property values String pname = attributes.getValue("name"); //$NON-NLS-1$ String type = attributes.getValue("type"); //$NON-NLS-1$ propertyDoc.setName(pname); String scope = attributes.getValue("scope"); //$NON-NLS-1$ if (scope.equals("instance")) //$NON-NLS-1$ { propertyDoc.setIsInstance(true); } else if (scope.equals("invocation")) //$NON-NLS-1$ { propertyDoc.setIsInvocationOnly(true); } String[] types = type.split("\\s*\\|\\s*"); //$NON-NLS-1$ for (String propertyType : types) { if (propertyType != null && propertyType.length() > 0) { propertyDoc.getReturn().addType(propertyType); } } propertyDoc.getMemberOf().addType(_currentClass.getName()); // set current property this._currentProperty = propertyDoc; } /** * Exit a reference element * * @param ns * @param name * @param qname * @param attributes */ public void enterReference(String ns, String name, String qname, Attributes attributes) { // grab and set property values String rname = attributes.getValue("name"); //$NON-NLS-1$ // add buffered text this._references.add(rname); } /** * Exit a return-type element * * @param ns * @param name * @param qname * @param attributes */ public void enterReturnType(String ns, String name, String qname, Attributes attributes) { // grab and set property values String type = attributes.getValue("type"); //$NON-NLS-1$ // add to return-types list this._currentMethod.getReturn().addType(type); // this._returnTypes.add(type); this._inReturnType = true; this._returnTypeCount++; } /** * start processing a value element * * @param ns * @param name * @param qname * @param attributes */ public void enterValue(String ns, String name, String qname, Attributes attributes) { // create a new item documentation object TypedDescription field = new TypedDescription(); // grab and set property values String fieldName = attributes.getValue("name"); //$NON-NLS-1$ field.setName(fieldName); String fieldType = attributes.getValue("description"); //$NON-NLS-1$ field.setDescription(fieldType); this._currentValue = field; } /** * Exit a browser element * * @param ns * @param name * @param qname */ public void exitBrowser(String ns, String name, String qname) { if (this._currentProperty != null) { // add example to the current property this._currentProperty.addUserAgent(this._currentUserAgent); } else if (this._currentMethod != null) { // add description to the current method this._currentMethod.addUserAgent(this._currentUserAgent); } else if (this._currentClass != null) { // add description to the current method this._currentClass.addUserAgent(this._currentUserAgent); } // clear current class this._currentUserAgent = null; } /** * Exit a class element * * @param ns * @param name * @param qname */ public void exitClass(String ns, String name, String qname) { // clear current method this._currentClass = null; } /** * Exit a constructors element * * @param ns * @param name * @param qname */ public void exitConstructors(String ns, String name, String qname) { _parsingCtors = false; } /** * Exit a deprecated element * * @param ns * @param name * @param qname */ public void exitDeprecated(String ns, String name, String qname) { if (this._currentProperty != null) { // add example to the current property this._currentProperty.setDeprecatedDescription(this._textBuffer); } else if (this._currentMethod != null) { // add description to the current method this._currentMethod.setDeprecatedDescription(this._textBuffer); } else if (this._currentClass != null) { // add description to the current method this._currentClass.setDeprecatedDescription(this._textBuffer); } else { // throw error } // clear buffer and reset text buffering state this._textBuffer = StringUtils.EMPTY; this._bufferText = false; } /** * Exit a description element * * @param ns * @param name * @param qname */ public void exitDescription(String ns, String name, String qname) { if (this._currentParameter != null) { // add example to the current parameter this._currentParameter.setDescription(this._textBuffer); } else if (this._currentException != false) { // ignore this._currentException = (this._currentException == false ) ? false : true; } else if (this._currentProperty != null) { // add example to the current property this._currentProperty.setDescription(this._textBuffer); } else if (this._currentMethod != null) { if (this._inReturnType) { String description = this._currentMethod.getDescription(); String[] returnTypes = this._currentMethod.getReturn().getTypes(); String text = ""; //$NON-NLS-1$ if (this._returnTypeCount == 1) { text = "<b>Returns:</b><br>"; //$NON-NLS-1$ } if (returnTypes != null && returnTypes.length > 0) { String returnType = returnTypes[returnTypes.length - 1]; text += "<b>" + returnType + "</b>: " + this._textBuffer; //$NON-NLS-1$ //$NON-NLS-2$ } else { text += this._textBuffer; } if (description != null && description.length() > 0) { description += "<br><br>" + text; //$NON-NLS-1$ } else { description = text; } // add description to the current method this._currentMethod.setDescription(description); } else { this._currentMethod.setDescription(this._textBuffer); } } else if (this._currentClass != null) { // add description to the current method this._currentClass.setDescription(this._textBuffer); } else if (this._currentProject != null) { // add description to the current method this._currentProject.setDescription(this._textBuffer); } else if (this._currentUserAgent != null) { // add description to the current method this._currentUserAgent.setDescription(this._textBuffer); } else { // throw error } // clear buffer and reset text buffering state this._textBuffer = StringUtils.EMPTY; this._bufferText = false; } /** * Exit a example element * * @param ns * @param name * @param qname */ public void exitExample(String ns, String name, String qname) { if (this._currentProperty != null) { // add example to the current property this._currentProperty.addExample(this._textBuffer); } else if (this._currentMethod != null) { this._currentMethod.addExample(this._textBuffer); } else { this._currentClass.addExample(this._textBuffer); } // clear buffer and reset text buffering state this._textBuffer = ""; //$NON-NLS-1$ this._bufferText = false; } /** * Exit a exception element * * @param ns * @param name * @param qname */ public void exitException(String ns, String name, String qname) { this._currentException = false; } /** * Exit a javascript element * * @param ns * @param name * @param qname */ public void exitJavaScript(String ns, String name, String qname) { // create documentation container this._documentation = new ScriptDoc(); // add buffered docs to JavaScript documentation this._documentation.setProject(this._currentProject); this._documentation.setFunctions(this._functions.toArray(new FunctionDocumentation[this._functions.size()])); this._documentation.setProperties(this._properties.toArray(new PropertyDocumentation[this._properties.size()])); this._documentation.setAliases(this._aliases.toArray(new AliasEntry[this._aliases.size()])); // clear buffers this._functions.clear(); } /** * Exit a method element * * @param ns * @param name * @param qname */ public void exitMethod(String ns, String name, String qname) { // add class to class list this._functions.add(this._currentMethod); // clear current method this._currentMethod = null; } /** * Exit a parameter element * * @param ns * @param name * @param qname */ public void exitParameter(String ns, String name, String qname) { if (this._currentParameter == null) { throw new IllegalArgumentException(Messages.ScriptDocReader_ParamNullError); } // add parameter to parameter list this._currentMethod.addParam(this._currentParameter); // clear current parameter this._currentParameter = null; } /** * Exit a property element * * @param ns * @param name * @param qname */ public void exitProperty(String ns, String name, String qname) { if (this._currentProperty == null) { throw new IllegalArgumentException(Messages.ScriptDocReader_PropertyNullError); } // add property to property list this._properties.add(this._currentProperty); // clear current property this._currentProperty = null; } /** * Exit a remarks element * * @param ns * @param name * @param qname */ public void exitRemarks(String ns, String name, String qname) { if (this._currentProperty != null) { // add remarks to the current property this._currentProperty.setRemarks(this._textBuffer); } else if (this._currentMethod != null) { this._currentMethod.setRemarks(this._textBuffer); } else { this._currentClass.setRemarks(this._textBuffer); } // reset buffer and clear buffer flash this._textBuffer = ""; //$NON-NLS-1$ this._bufferText = false; } /** * Exit a description element * * @param ns * @param name * @param qname */ public void exitReturnDescription(String ns, String name, String qname) { if (this._currentMethod != null) { // add description to the current method this._currentMethod.getReturn().setDescription(this._textBuffer); } // clear buffer and reset text buffering state this._textBuffer = ""; //$NON-NLS-1$ this._bufferText = false; } /** * Exit a return-type element * * @param ns * @param name * @param qname */ public void exitReturnType(String ns, String name, String qname) { this._inReturnType = false; } /** * Exit a field element * * @param ns * @param name * @param qname */ public void exitValue(String ns, String name, String qname) { // add class to class list this._currentParameter.addDefaultValue(this._currentValue); // clear current class this._currentValue = null; } /** * getAptanaDocumentationStream * * @param stream * @return */ private InputStream getAptanaDocumentationStream(InputStream stream) { String source = null; try { // grab all the text so we can analyze it source = StreamUtils.getText(stream, "UTF-8"); } catch (IOException e) { } finally { // make sure we have something to work with if (source == null) { source = ""; //$NON-NLS-1$ } } // create a new stream that we can use to determine the XML's default // namespace InputStream input = new ByteArrayInputStream(source.getBytes()); try { NamespaceSniffer sniffer = new NamespaceSniffer(); String namespace; sniffer.read(input); namespace = sniffer.getNamespace(); if (namespace != null && namespace.length() > 0) { ForeignScriptDocInfo info = ForeignScriptDocManager.getInstance().getInfo(namespace); if (info != null) { InputStream stylesheetStream = info.getStylesheetInputStream(); if (stylesheetStream != null) { // transform input source = this.transformToAptana(source, stylesheetStream); } else { IdeLog.logError( JSPlugin.getDefault(), MessageFormat.format( "XSL Transform stylesheet for the {0} namespace does not exist: {1}", //$NON-NLS-1$ new Object[] { info.namespace, info.stylesheet.toString() } ) ); } } } } catch (ParserConfigurationException e) { } catch (IOException e) { } finally { try { input.close(); } catch (IOException e) { } } // return an input stream of whatever source we ended up with return new ByteArrayInputStream(source.getBytes()); } /** * Get the resulting documentation object created after loading a documentation document * * @return The resulting documentation object */ public ScriptDoc getDocumentation() { return this._documentation; } /** * transformOAAtoAptana * * @param source * @return */ private String transformToAptana(String source, InputStream stylesheetStream) { String result = null; // Create a transform factory instance. TransformerFactory factor = TransformerFactory.newInstance(); // Create input/output streams StreamSource stylesheetSource = new StreamSource(stylesheetStream); InputStream sourceStream = new ByteArrayInputStream(source.getBytes()); StreamSource sourceSource = new StreamSource(sourceStream); StringWriter sourceWriter = new StringWriter(); StreamResult resultStream = new StreamResult(sourceWriter); try { // Create a transformer for the stylesheet. Transformer transformer = factor.newTransformer(stylesheetSource); // Transform the source XML to System.out. transformer.transform(sourceSource, resultStream); result = sourceWriter.toString(); } catch (TransformerConfigurationException e) { IdeLog.logError(JSPlugin.getDefault(), "XSL Transform configuration error", e); //$NON-NLS-1$ } catch (TransformerException e) { IdeLog.logError(JSPlugin.getDefault(), "XSL Transformation error", e); //$NON-NLS-1$ } finally { try { stylesheetStream.close(); } catch (IOException e) { } try { sourceStream.close(); } catch (IOException e) { } try { sourceWriter.close(); } catch (IOException e) { } } return result; } /** * Load the JavaScript built-in objects documentation using a stream. * * @param stream * The input stream for the source XML * @throws ScriptDocException */ public void loadXML(InputStream stream1) throws ScriptDocException { InputStream stream = this.getAptanaDocumentationStream(stream1); // create a new SAX factory class SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); // clear properties this._textBuffer = ""; //$NON-NLS-1$ SAXParser saxParser = null; // parse the XML file try { saxParser = factory.newSAXParser(); saxParser.parse(stream, this); } catch (ParserConfigurationException e) { String msg = Messages.ScriptDocReader_SaxError; ScriptDocException de = new ScriptDocException(msg, e); throw de; } catch (SAXException e) { Exception ex = e.getException(); String msg = Messages.ScriptDocReader_ParseError; if (ex != null) { msg += ex.getMessage(); } else { msg += e.getMessage(); } ScriptDocException de = new ScriptDocException(msg, e); throw de; } catch (IOException e) { String msg = Messages.ScriptDocReader_IOParseError; ScriptDocException de = new ScriptDocException(msg, e); throw de; } } /** * Load the JavaScript built-in objects documentation * * @param filename * @throws ScriptDocException */ public void loadXML(String filename) throws ScriptDocException { FileInputStream fi = null; try { fi = new FileInputStream(filename); this.loadXML(fi); } catch (FileNotFoundException e) { String msg = Messages.ScriptDocReader_XMLLocationError + filename; ScriptDocException de = new ScriptDocException(msg, e); throw de; } finally { try { fi.close(); } catch (IOException e) { } } } /** * start buffering text * * @param ns * @param name * @param qname * @param attributes */ public void startTextBuffer(String ns, String name, String qname, Attributes attributes) { this._bufferText = true; } }