/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.indexer; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Memory-optimized call path like $a->method()->$b->$c->method2() * * @author Denis Denisenko */ public class CallPath { /** * Path entry. May be either VariableEntry or MethodEntry * * @author Denis Denisenko */ public abstract static class Entry // $codepro.audit.disable noAbstractMethods { /** * Entry name. */ private String name; /** * Entry constructor. * * @param name * - entry name. */ protected Entry(String name) { this.name = name; } /** * Gets entry name * * @return name. */ public String getName() { return name; } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Entry other = (Entry) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } /** * Variable entry. * * @author Denis Denisenko */ public final static class VariableEntry extends Entry { /** * VariableEntry constructor. * * @param name * - variable name. */ public VariableEntry(String name) { super(name); } /** * {@inheritDoc} */ @Override public String toString() { return "var: " + getName(); //$NON-NLS-1$ } } /** * Class entry. * * @author Denis Denisenko */ public final static class ClassEntry extends Entry { /** * ClassEntry constructor. * * @param name * - class name. */ public ClassEntry(String name) { super(name); } /** * {@inheritDoc} */ @Override public String toString() { return "class: " + getName(); //$NON-NLS-1$ } } /** * Method entry. * * @author Denis Denisenko */ public final static class MethodEntry extends Entry { /** * MethodEntry constructor. * * @param name * - variable name. */ public MethodEntry(String name) { super(name); } /** * {@inheritDoc} */ @Override public String toString() { return "func: " + getName(); //$NON-NLS-1$ } } /** * Masks for bit access. */ private final static int[] _masks = { 0x80000000, 0x40000000, 0x20000000, 0x10000000, 0x08000000, 0x04000000, 0x02000000, 0x01000000, 0x00800000, 0x00400000, 0x00200000, 0x00100000, 0x00080000, 0x00040000, 0x00020000, 0x00010000, 0x00008000, 0x00004000, 0x00002000, 0x00001000, 0x00000800, 0x00000400, 0x00000200, 0x00000100, 0x00000080, 0x00000040, 0x00000020, 0x00000010, 0x00000008, 0x00000004, 0x00000002, 0x00000001 }; /** * Entry names. */ private List<String> names = new ArrayList<String>(1); /** * Entry types. */ private int types; /** * Whether first entry is of a ClassEntry type. */ private boolean firstEntryOfClassType = false; /** * Almost never used. Is required for the paths with the length greater then 31. */ private List<Boolean> typesArray = null; /** * Gets size. * * @return path size. */ public int getSize() { return names.size(); } /** * Sets first entry as a class entry. * * @param name * - class name. */ public void setClassEntry(String name) { if (names.size() != 0) { throw new IllegalArgumentException("Only the first entry might have Class type"); //$NON-NLS-1$ } firstEntryOfClassType = true; names.add(name); } /** * Adds variable entry to the right of the path. * * @param name * - variable name. */ public void addVariableEntry(String name) { if (names.size() == 31) { typesArray = new ArrayList<Boolean>(32); typesArray.add(true); } else if (names.size() > 31) { typesArray.add(true); } else { // sets the appropriate bit to 1 types |= _masks[names.size()]; } names.add(name); } /** * Adds method entry to the right of the path. * * @param name * - method name. */ public void addMethodEntry(String name) { if (names.size() == 31) { typesArray = new ArrayList<Boolean>(32); typesArray.add(false); } else if (names.size() > 31) { typesArray.add(false); } // else - appropriate bit is already 0 so doing nothing names.add(name); } /** * Adds the specified path as a tail to the current path. * * @param path * - path to add. */ public void addPath(CallPath path) { for (CallPath.Entry entry : path.getEntries()) { if (entry instanceof ClassEntry) { throw new IllegalArgumentException( "Class entry must start the path, so path containing Class entry can not be added."); //$NON-NLS-1$ } else if (entry instanceof MethodEntry) { addMethodEntry(entry.getName()); } else if (entry instanceof VariableEntry) { addVariableEntry(entry.getName()); } else { throw new IllegalArgumentException("Unknown entry type: " + entry.getClass().getCanonicalName()); //$NON-NLS-1$ } } } /** * Inserts variable entry to the beginning of the path. * * @param name * - variable name. */ public void insertVariableEntry(String name) { if (names.size() == 31) { typesArray = new ArrayList<Boolean>(32); typesArray.add(0, true); } else if (names.size() > 31) { typesArray.add(0, true); } else { // shifting types types = types >>> 1; // sets the appropriate bit to 1 types |= _masks[0]; } names.add(0, name); } /** * Inserts method entry to the beginning of the path. * * @param name * - method name. */ public void insertMethodEntry(String name) { if (names.size() == 31) { typesArray = new ArrayList<Boolean>(32); typesArray.add(0, false); } else if (names.size() > 31) { typesArray.add(0, false); } else { // shifting types types = types >>> 1; // appropriate bit is already 0 so setting nothing } names.add(0, name); } /** * Returns sub path. * * @param start * - index of sub path start. * @return sub path. */ public CallPath subPath(int start) { if (start >= names.size()) { return new CallPath(); } CallPath result = new CallPath(); List<Entry> entries = getEntries(); for (int i = start; i < entries.size(); i++) { Entry entry = entries.get(i); if (entry instanceof VariableEntry) { result.addVariableEntry(entry.getName()); } else { result.addMethodEntry(entry.getName()); } } return result; } /** * Gets entries. * * @return entries. */ public List<Entry> getEntries() { List<Entry> result = new ArrayList<Entry>(names.size()); for (int i = 0; i < names.size(); i++) { if (i == 0 && firstEntryOfClassType) { result.add(new ClassEntry(names.get(i))); } else { if (getType(i)) { result.add(new VariableEntry(names.get(i))); } else { result.add(new MethodEntry(names.get(i))); } } } return result; } /** * Gets type for the position. * * @param i * - position. * @return true if variable, false if method. */ private boolean getType(int i) { if (names.size() > 31) { return typesArray.get(i); } else { return (types & _masks[i]) != 0; } } /** * {@inheritDoc} */ @Override public String toString() { return getEntries().toString(); } /** * Gets whether current path is equal to the other path. * * @param other * - path to compare to. * @return */ public boolean compare(CallPath other) { if (names.size() != other.names.size()) { return false; } if (typesArray != null) { if (other.typesArray == null) { return false; } if (!typesArray.equals(other.typesArray)) { return false; } } else { if (types != other.types) { return false; } } if (!names.equals(other.names)) { return false; } return true; } public void write(DataOutputStream da) throws IOException { int size = names.size(); da.writeInt(size); da.writeInt(types); da.writeBoolean(firstEntryOfClassType); for (String s : names) { da.writeUTF(s); } da.writeBoolean(typesArray != null); if (typesArray != null) { da.writeInt(typesArray.size()); for (Boolean b : typesArray) { da.writeBoolean(b); } } } public CallPath() { } public CallPath(DataInputStream di) throws IOException { int readInt = di.readInt(); names = new ArrayList<String>(readInt); types = di.readInt(); firstEntryOfClassType = di.readBoolean(); for (int a = 0; a < readInt; a++) { names.add(di.readUTF().intern()); } if (di.readBoolean()) { int sz = di.readInt(); typesArray = new ArrayList<Boolean>(sz); for (int a = 0; a < sz; a++) { typesArray.add(di.readBoolean()); } } } }