/** * 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.js.runtime; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.aptana.ide.core.IdeLog; import com.aptana.ide.core.StringUtils; import com.aptana.ide.editor.js.JSPlugin; import com.aptana.ide.lexer.IRange; import com.aptana.ide.metadata.IDocumentation; /** * @author Kevin Lindsey * @author Robin Debreuil */ public abstract class ObjectBase implements IObject { /** * NULL singleton */ public static final JSNull NULL = JSNull.getSingletonInstance(); /** * UNDEFINED singleton */ public static final JSUndefined UNDEFINED = JSUndefined.getSingletonInstance(); private IObject _prototype; private Map<String,Property> _properties; private IDocumentation _documentation; private IRange _range; /** * Get the property hash table for this object. The hash is lazily instantiated so all access to * property hash should be done through this method * * @return Returns the property hash table */ private Map<String,Property> getProperties() { if (this._properties == null) { this._properties = new HashMap<String,Property>(); } return this._properties; } /** * Create a new instance of ObjectBase */ public ObjectBase() { this(null); } /** * Creat a new instance of ObjectBase * * @param range * The range of text within the source file that represents this object */ public ObjectBase(IRange range) { this._range = range; } /* * IObject implementation */ /** * @see com.aptana.ide.editor.js.runtime.IObject#canPut(java.lang.String) */ public boolean canPut(String propertyName) { boolean result = true; if (this._properties != null && this.getProperties().containsKey(propertyName)) { Property p = this.getLocalProperty(propertyName); result = (p.isReadOnly() == false); } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getProperty(java.lang.String) */ public Property getProperty(String propertyName) { Property result = null; if (this._properties != null && this.getProperties().containsKey(propertyName)) { result = this.getLocalProperty(propertyName); } else { if (this._prototype != null) { result = this._prototype.getProperty(propertyName); } } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getPropertyValue(java.lang.String, int, int) */ public IObject getPropertyValue(String propertyName, int fileIndex, int offset) { IObject result = ObjectBase.UNDEFINED; if (this._properties != null && this.getProperties().containsKey(propertyName)) { Property p = this.getLocalProperty(propertyName); result = p.getValue(fileIndex, offset); } else { if (this._prototype != null) { result = this._prototype.getPropertyValue(propertyName, fileIndex, offset); } } // XXX Not sure if we should be mixing CommandNode logic at this level, // but we need this code to allow // descendants of a command node to be used as the value so that we can // control the offset being used when // storing a value. In particular, any of the binary operators that // assign need to use their operator as the // offset when storing the value // if (result instanceof CommandNode) // { // CommandNode node = (CommandNode) result; // CommandNode parent = node.getParentNode(); // // if (parent instanceof BinaryOperatorAssignNode) // { // result = parent; // } // } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#putPropertyValue(java.lang.String, * com.aptana.ide.editor.js.runtime.IObject, int) */ public void putPropertyValue(String propertyName, IObject value, int fileIndex) { this.putPropertyValue(propertyName, value, fileIndex, Property.NONE); } /** * @see com.aptana.ide.editor.js.runtime.IObject#putPropertyValue(java.lang.String, * com.aptana.ide.editor.js.runtime.IObject, int, int) */ public void putPropertyValue(String propertyName, IObject value, int fileIndex, int attributes) { if (this.canPut(propertyName)) { Map<String,Property> properties = this.getProperties(); Property p; if ("Jaxer".equals(propertyName)) //$NON-NLS-1$ { String message = "putPropertyValue put " + value + "\n" + this.getStackTrace(); //$NON-NLS-1$ //$NON-NLS-2$ IdeLog.logInfo(JSPlugin.getDefault(), message); } if (properties.containsKey(propertyName)) { p = this.getLocalProperty(propertyName); try { p.setValue(value, fileIndex); } catch (IllegalStateException e) { IdeLog.logError( JSPlugin.getDefault(), StringUtils.format( Messages.ObjectBase_AttemptedToOverwritePropertyNameAtFileIndex, new String[] { propertyName, String.valueOf(fileIndex) } ), e ); } } else { p = new Property(value, fileIndex, attributes); properties.put(propertyName, p); } p.addReference(); } } /** * @see com.aptana.ide.editor.js.runtime.IObject#getPropertyNames() */ public String[] getPropertyNames() { return this.getPropertyNames(false); } /** * @see com.aptana.ide.editor.js.runtime.IObject#getPropertyNames(boolean) */ public String[] getPropertyNames(boolean getAll) { Map<String,Boolean> result = new HashMap<String,Boolean>(); IObject current = this; while (current != null) { String[] localNames = current.getLocalPropertyNames(); if (getAll) { for (int i = 0; i < localNames.length; i++) { result.put(localNames[i], Boolean.TRUE); } } else { for (int i = 0; i < localNames.length; i++) { String name = localNames[i]; if (result.containsKey(name) == false) { Property p = current.getLocalProperty(name); if (p.isEnumerable()) { result.put(localNames[i], Boolean.TRUE); } } } } current = current.getPrototype(); } Set<String> s = result.keySet(); return s.toArray(new String[s.size()]); } /** * @see com.aptana.ide.editor.js.runtime.IObject#hasProperty(java.lang.String) */ public boolean hasProperty(String propertyName) { boolean result = false; if (this._properties != null && this.getProperties().containsKey(propertyName)) { result = true; } else { if (this._prototype != null) { result = this._prototype.hasProperty(propertyName); } } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getLocalProperty(java.lang.String) */ public Property getLocalProperty(String propertyName) { Property result = null; if (this._properties != null) { Map<String,Property> properties = this.getProperties(); if (properties.containsKey(propertyName) == false) { throw new IllegalArgumentException(Messages.ObjectBase_LocalPropertyNameDoesNotExist + propertyName); } result = properties.get(propertyName); } else { throw new IllegalArgumentException(Messages.ObjectBase_LocalPropertyNameDoesNotExist2 + propertyName); } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#putLocalProperty(java.lang.String, * com.aptana.ide.editor.js.runtime.Property) */ public void putLocalProperty(String propertyName, Property property) { Map<String,Property> properties = this.getProperties(); if ("Jaxer".equals(propertyName)) //$NON-NLS-1$ { String message = "putLocalProperty put Jaxer\n" + this.getStackTrace(); //$NON-NLS-1$ IdeLog.logInfo(JSPlugin.getDefault(), message); } properties.put(propertyName, property); } /** * @see com.aptana.ide.editor.js.runtime.IObject#getLocalPropertyNames() */ public String[] getLocalPropertyNames() { String[] result; if (this._properties != null) { Set<String> names = this.getProperties().keySet(); result = names.toArray(new String[names.size()]); } else { result = new String[0]; } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#hasLocalProperty(java.lang.String) */ public boolean hasLocalProperty(String propertyName) { boolean result = false; if (this._properties != null) { result = this.getProperties().containsKey(propertyName); } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#unputPropertyName(java.lang.String, int, int) */ public void unputPropertyName(String propertyName, int fileIndex, int offset) { if (propertyName == null || propertyName.length() == 0) { throw new NullPointerException(Messages.ObjectBase_PropertyNameMustBeDefined); } Property p = this.getProperty(propertyName); if (p == null) { throw new NullPointerException(Messages.ObjectBase_TryingToUnputAPropertyThatDoesNotExist + propertyName); } if (p.removeReference() == 0) { if (p.isPermanent() == false) { this.deletePropertyName(propertyName); } } else { if ("Jaxer".equals(propertyName)) //$NON-NLS-1$ { String message = "unputPropertyValue:\n" + this.getStackTrace(); //$NON-NLS-1$ IdeLog.logInfo(JSPlugin.getDefault(), message); } p.unsetValue(fileIndex, offset); } } /** * @see com.aptana.ide.editor.js.runtime.IObject#deletePropertyName(java.lang.String) */ public boolean deletePropertyName(String propertyName) { boolean result = true; if (this._properties != null) { Map<String,Property> properties = this.getProperties(); if (properties.containsKey(propertyName)) { Property p = this.getLocalProperty(propertyName); if (p.isReadOnly()) { result = false; } else { if ("Jaxer".equals(propertyName)) //$NON-NLS-1$ { String message = "deletePropertyName deleted Jaxer\n" + this.getStackTrace(); //$NON-NLS-1$ IdeLog.logInfo(JSPlugin.getDefault(), message); } properties.remove(propertyName); } } } return result; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getPrototype() */ public IObject getPrototype() { return this._prototype; } /** * @see com.aptana.ide.editor.js.runtime.IObject#setPrototype(com.aptana.ide.editor.js.runtime.IObject) */ public void setPrototype(IObject prototype) { this._prototype = prototype; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getClassName() */ public abstract String getClassName(); /** * @see com.aptana.ide.editor.js.runtime.IObject#getInstance(com.aptana.ide.editor.js.runtime.Environment, * int, int) */ public IObject getInstance(Environment environment, int fileIndex, int offset) { // built-in and primitive types simply return "this" return this; } /* * IRange implementation */ /** * @see com.aptana.ide.lexer.IRange#getEndingOffset() */ public int getEndingOffset() { int result = -1; if (this._range != null) { result = this._range.getEndingOffset(); } return result; } /** * @see com.aptana.ide.lexer.IRange#getLength() */ public int getLength() { int result = 0; if (this._range != null) { result = this._range.getLength(); } return result; } /** * Return the range used to define this object * * @return Returns this object's range in the source document */ public IRange getRange() { return this._range; } /** * @see com.aptana.ide.lexer.IRange#getStartingOffset() */ public int getStartingOffset() { int result = -1; if (this._range != null) { result = this._range.getStartingOffset(); } return result; } /** * @see com.aptana.ide.lexer.IRange#isEmpty() */ public boolean isEmpty() { boolean result = true; if (this._range != null) { result = this._range.isEmpty(); } return result; } /** * @see com.aptana.ide.lexer.IRange#containsOffset(int) */ public boolean containsOffset(int offset) { boolean result = false; if (this._range != null) { result = this._range.containsOffset(offset); } return result; } /* * IDocumentationContainer implementation */ /** * @see com.aptana.ide.metadata.IDocumentationContainer#getDocumentation() */ public IDocumentation getDocumentation() { return this._documentation; } /** * @see com.aptana.ide.metadata.IDocumentationContainer#hasDocumentation() */ public boolean hasDocumentation() { return this._documentation != null; } /** * @see com.aptana.ide.metadata.IDocumentationContainer#setDocumentation(com.aptana.ide.metadata.IDocumentation) */ public void setDocumentation(IDocumentation documentation) { this._documentation = documentation; } /** * @see com.aptana.ide.editor.js.runtime.IObject#getLocalPropertyCount() */ public int getLocalPropertyCount() { int result = 0; if (this._properties != null) { result = this._properties.size(); } return result; } /** * [KEL] temporary for debugging purposes only * * getStackTrace * @return */ private String getStackTrace() { StackTraceElement[] elements = Thread.currentThread().getStackTrace(); StringBuilder sb = new StringBuilder(); for (StackTraceElement element : elements) { sb.append(element.toString()).append("\n"); //$NON-NLS-1$ } return sb.toString(); } }