/* * Rewritten to use generics (ironic, isn't it?), 6/06. */ package eug.shared; import java.io.BufferedWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Vector; /** * This class represents an object present in the game file, * whatever its type. It has four main vectors for storing descendants: * <ul> * <li>{@link #children}: a vector which contains all the objects beneath it. * (For example, populations for a province object) * <li>{@link #values}: same, but for the attributes.<br> * Example: <PRE>"ID = 45"</PRE> * <li>{@link #lists}: same, but for lists.<br> * Example: <PRE>"ownedprovinces = { 45 46 50 }"</PRE> * <li>{@link #allWritable}: this vector contains all the descendants. * EVERY OBJECT PRESENT IN <CODE>children</CODE>, <CODE>values</CODE> OR * <CODE>lists</CODE> MUST BE IN IT, OR THEY WILL NOT BE SAVED.<br> * It is used to preserve the original order of descendants when saving the file * (not required by the game, but anything else would be very confusing for users). * </ul> * <p> * Unless otherwise noted, all get/set variable operations (e.g., * <code>getString()</code>) are case insensitive. */ public final class GenericObject implements WritableObject, Cloneable { // <editor-fold defaultstate="collapsed" desc=" Variables "> // The header and inline comments, if any, are not stored in allWritable. private HeaderComment headComment = null; private InlineComment inlineComment = null; //modif leo public List<GenericObject> children; public List<GenericList> lists; private GenericObject parent; public List<ObjectVariable> values; /** @since EUGFile 1.01.03 */ private List<Comment> generalComments; // comments that are not attached to a node public String name; private List<WritableObject> allWritable; public static final String tab = " "; public static final int tabLength = 4; // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Constructors "> /** * Creates a root object. Use * {@link #createChild(GenericObject,String) createChild} to create * non-root objects. */ public GenericObject() { this(null, "root"); } private GenericObject(GenericObject par, String t) { parent = par; name = t; // Consider using ArrayLists instead. // ArrayLists would probably be a little faster, but are not thread-safe; // however, I don't know if thread safety will ever be an issue. children = new Vector<GenericObject>(); lists = new Vector<GenericList>(); values = new Vector<ObjectVariable>(); generalComments = new Vector<Comment>(); allWritable = new Vector<WritableObject>(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Getters "> public GenericObject getChild(int id) { if (id >= children.size()) return null; else return children.get(id); } public GenericObject getChild(String childname) { for (GenericObject child : children) if (child.name.equalsIgnoreCase(childname)) return child; return null; } public List<GenericObject> getChildren(String name) { final List<GenericObject> ret = new ArrayList<GenericObject>(); for (GenericObject child : children) if (child.name.equalsIgnoreCase(name)) ret.add(child); return ret; } public GenericList getList(String listname) { for (GenericList list : lists) if (list.getName().equalsIgnoreCase(listname)) return list; GenericObject l = getChild(listname); if (l == null) return null; if (l.allWritable.size() == 0) // which means it could have been an empty list parsed as an object with no children return convertToList(l); return null; } private GenericList convertToList(GenericObject child) { GenericList newList = new GenericList(child.name); final int idx = allWritable.indexOf(child); allWritable.set(idx, newList); children.remove(child); lists.add(newList); return newList; } public GenericList getList(int id) { if (id >= lists.size()) return null; else return lists.get(id); } public ObjectVariable getVariable(int id) { if (id >= values.size()) return null; else return values.get(id); } public String getString(String varname) { for (ObjectVariable var : values) if (var.varname.equalsIgnoreCase(varname)) return var.getValue(); return ""; } /** * Returns all values of the given string. For example, if an object * contains something like * <pre> * vp = 100 * vp = 101 * </pre> * , then <code>getStrings("vp")</code> will return a list containing "100" * and "101". * @since EUGFile 1.02.00 */ public List<String> getStrings(String name) { final List<String> ret = new ArrayList<String>(); for (ObjectVariable var : values) if (var.varname.equalsIgnoreCase(name)) ret.add(var.getValue()); return ret; } public int getInt(String varname) { final String val = getString(varname); if (val.length() != 0) return Integer.valueOf(val); else return -1; } public double getDouble(String varname) { final String val = getString(varname); if (val.length() != 0) return Double.valueOf(val); else return -1.0; } public GenericObject getParent() { return parent; } /** * Simple recursive method to find the root node. * @since EUGFile 1.01.03 */ public GenericObject getRoot() { return (parent == null ? this : parent.getRoot()); } /** * This method should only be used with great, great caution. Modifying the * returned list could cause major problems. */ public List<WritableObject> getAllWritable() { return allWritable; } // Doesn't exactly belong in this section, but it is a getter, so... /** * Returns the comment immediately following the variable with the given * name. * @since 1.02.00 */ public String getCommentAfterVar(String varname) { for (int i = 0; i < allWritable.size(); i++) { WritableObject obj = allWritable.get(i); if (obj instanceof ObjectVariable) { ObjectVariable var = (ObjectVariable) obj; if (!var.varname.equalsIgnoreCase(varname)) continue; if (var.getInlineComment().length() != 0) return var.getInlineComment(); WritableObject next = allWritable.get(i+1); if (next instanceof ObjectVariable) { return ((ObjectVariable) next).getHeadComment(); } else if (next instanceof GenericObject) { return ((GenericObject) next).getHeadComment(); } else if (next instanceof GenericList) { return ((GenericList) next).getHeaderComment(); } else if (next instanceof Comment) { return ((Comment) next).getComment(); } else { return ""; } } } return ""; } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Counters "> public int nbChild() { return children.size(); } public int nbChild(String type) { int nb = 0; for (GenericObject child : children) { if (child.name.equalsIgnoreCase(type)) nb++; } return nb; } public int nbList() { return lists.size(); } public int nbVar() { return values.size(); } public int nbVar(String type) { int nb = 0; for (ObjectVariable var : values) if (var.varname.equalsIgnoreCase(type)) nb++; return nb; } public int size() { return allWritable.size(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Mutators "> public void removeVariable(int id) { allWritable.remove(values.get(id)); values.remove(id); } public boolean removeVariable(String name) { ObjectVariable var = null; for (int i = 0; i < values.size(); i++) { if (values.get(i).varname.equalsIgnoreCase(name)) { var = values.get(i); values.remove(i); break; } } if (var == null) return false; allWritable.remove(var); return true; } public void removeChild(GenericObject val){ this.children.remove(val); this.allWritable.remove(val); } public boolean removeChild(String name) { GenericObject obj = getChild(name); if (obj != null) { removeChild(obj); return true; } return false; } public void removeList(GenericList list) { this.lists.remove(list); this.allWritable.remove(list); } public boolean removeList(String listname) { for (int i = 0; i < lists.size(); i++) { if (lists.get(i).getName().equalsIgnoreCase(listname)) { allWritable.remove(lists.get(i)); lists.remove(i); return true; } } return false; } public void setString(String varname, String newval) { setString(varname, newval, false); } public void setString(String varname, String newval, boolean quotes) { for (ObjectVariable var : values) { if (var.varname.equalsIgnoreCase(varname)) { var.setValue(newval, quotes); return; } } //pas de variable de ce nom, on la cr�e this.addString(varname, newval, quotes); } public void setInt(String varname, int newval) { setString(varname, String.valueOf(newval)); } public void setDouble(String varname, double newval) { setString(varname, String.valueOf(newval)); } public void setBoolean(String varname, boolean newval) { setString(varname, (newval ? "yes" : "no")); } public void clear() { children.clear(); lists.clear(); values.clear(); allWritable.clear(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Queries "> public int isChildOf(GenericObject parent){ return parent.children.indexOf(this); } public boolean hasString(String varname) { for (ObjectVariable var : values) { if (var.varname.equalsIgnoreCase(varname)) return true; } return false; } public boolean contains(String str) { return hasString(str); } public boolean containsChild(String name) { return getChild(name) != null; } public boolean containsList(String name) { return getList(name) != null; } public boolean isEmpty() { return allWritable.isEmpty(); } /** * Returns whether this node is a root node or not. * @since EUGFile 1.02.00 */ public boolean isRoot() { return parent == null; } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Adders "> public void addString(String varname, String val) { addString(varname, val, false); } public void addString(String varname, String val, boolean quotes) { ObjectVariable newvariable = new ObjectVariable(varname, val, quotes); values.add(newvariable); allWritable.add(newvariable); } public void addString(String varname, String val, boolean quotes, String headComment, String inlineComment) { ObjectVariable newVar = new ObjectVariable(varname, val, quotes); newVar.setHeadComment(headComment); newVar.setInlineComment(inlineComment); values.add(newVar); allWritable.add(newVar); } // public void setChild(GenericObject newval, /*String type,*/ int pos){ // if (this.children.get(pos).equals(newval)) { // this.children.add(pos, newval); // return; // } // // this.addChild(newval); // } public void addChild(GenericObject newval){ if (newval == null) throw new NullPointerException("Can't add a null child"); this.children.add(newval); this.allWritable.add(newval); } public GenericObject createChild(String name) { final GenericObject child = new GenericObject(this, name); addChild(child); return child; } public void addList(GenericList newval) { this.lists.add(newval); this.allWritable.add(newval); } public void addList(String name, String[] vals) { final GenericList list = new GenericList(name); list.addAll(vals); addList(list); } public void addList(String name, List<String> vals) { final GenericList list = new GenericList(name); list.addAll(vals); addList(list); } public void addList(String name, String[] vals, String headComment, String inlineComment) { final GenericList list = new GenericList(name); list.addAll(vals); list.setHeaderComment(headComment); list.setInlineComment(inlineComment); addList(list); } public void addList(String name, List<String> vals, String headComment, String inlineComment) { final GenericList list = new GenericList(name); list.addAll(vals); list.setHeaderComment(headComment); list.setInlineComment(inlineComment); addList(list); } public GenericList createList(String name) { final GenericList list = new GenericList(name); addList(list); return list; } /** * Adds all children of <code>other</code> to this object. */ public void addAllChildren(GenericObject other) { for (WritableObject o : other.allWritable) { if (o instanceof GenericObject) { ((GenericObject) o).parent = this; this.children.add((GenericObject) o); } else if (o instanceof GenericList) { this.lists.add((GenericList) o); } else if (o instanceof ObjectVariable) { this.values.add((ObjectVariable) o); } this.allWritable.add(o); } } public void addNumber(String name, double val) { addString(name, String.format("%.3f", val), false); } public void addInt(String name, int val) { addString(name, Integer.toString(val), false); } public void addBoolean(String name, boolean val) { addString(name, (val ? "yes" : "no"), false); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Comments "> /** @since EUGFile 1.01.03 */ public void addGeneralComment(String comment) { // TODO: Figure out whether a given string is a header or inline comment. Currently the default is header. addGeneralComment(comment, true); } /** @since EUGFile 1.01.03 */ public void addGeneralComment(String comment, boolean header) { if (comment == null || comment.length() == 0) return; final Comment c = (header ? new HeaderComment(comment) : new InlineComment(comment)); generalComments.add(c); allWritable.add(c); } /** * Adds a comment to the beginning of the root object. Note that the last * comment added will appear first. * @since EUGFile 1.02.00 */ public void addFileHeaderComment(String comment, boolean header) { if (comment == null || comment.length() == 0) return; final Comment c = (header ? new HeaderComment(comment) : new InlineComment(comment)); final GenericObject root = getRoot(); // avoid calling recursive methods more than once root.generalComments.add(0, c); root.allWritable.add(0, c); } public String getHeadComment() { return headComment == null ? "" : headComment.getComment(); } public void setHeadComment(String comment) { if (comment == null || comment.length() == 0) { headComment = null; return; } headComment = new HeaderComment(comment); } public void addHeadComment(String comment) { if (headComment == null) headComment = new HeaderComment(comment); else headComment.appendComment(comment); } public void setInlineComment(String comment) { if (comment == null || comment.length() == 0) inlineComment = null; else inlineComment = new InlineComment(comment); } public String getInlineComment() { return (inlineComment == null ? "" : inlineComment.getComment()); } /** * Cause a blank line to appear in the output at the current index. * This could be useful for, e.g., adding the deathdate to an event object * and then adding a blank line before the commands. * @since EUGFile 1.04.00pre1 */ public void addBlankLine() { allWritable.add(new InlineComment("")); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" toFileString "> public void toFileString(final BufferedWriter bw) throws IOException { toFileString(bw, Style.DEFAULT); } public void toFileString(final BufferedWriter bw, final Style style) throws IOException { toFileString(bw, "", style); } public void toFileString(final BufferedWriter bw, String comment) throws IOException { toFileString(bw, comment, Style.DEFAULT); } public void toFileString(final BufferedWriter bw, String comment, Style style) throws IOException { //special starter method made for root //root is a "virtual" document node that does not appear in file //so we start by writing children directly if (comment != null && comment.length() != 0) { // if (allWritable.get(0) != null && allWritable.get(0) instanceof Comment) { // // If there is a file header comment, merge the two, with the new comment first. // Comment c = ((Comment) allWritable.get(0)); // String s = c.getComment(); // c.setComment(comment); // c.appendComment(s); // } else { new HeaderComment(comment).toFileString(bw, 0, style); // bw.newLine(); bw.newLine(); // } } for (WritableObject obj : allWritable) { obj.toFileString(bw, 0, style); bw.newLine(); } bw.close(); } public void toFileString(final BufferedWriter bw, int depth) throws IOException { toFileString(bw, depth, Style.DEFAULT); } public void toFileString(final BufferedWriter bw, int depth, Style style) throws IOException { if (name.equals("root")) { toFileString(bw, style); return; } final boolean parentSameLine = style.isInline(parent); final boolean sameLine = parentSameLine || style.isInline(this); if (!parentSameLine && headComment != null) { headComment.toFileString(bw, depth, style); } final String localtab = (parentSameLine ? "" : style.getTab(depth)); bw.write(localtab); if (!"".equals(name)) { bw.write(name); style.printEqualsSign(bw, depth); bw.write("{ "); } else { bw.write("{ "); } if (!sameLine) bw.newLine(); for (WritableObject obj : allWritable) { if (obj instanceof ObjectVariable || obj instanceof Comment) { if (!sameLine) { style.printTab(bw, depth+1); } } obj.toFileString(bw, depth+1, style); if (sameLine) bw.write(' '); else bw.newLine(); } if (parentSameLine) { bw.write('}'); } else if (sameLine) { bw.write("} "); if (inlineComment != null) inlineComment.toFileString(bw, depth, style); } else { bw.write(localtab + "} "); if (inlineComment != null) inlineComment.toFileString(bw, depth, style); if (style.newLineAfterObject()) bw.newLine(); } } // private boolean isSameLine() { // // Same line <=> the total number of children is <= 3, // // UNLESS this is an event trigger or action OR a child has an inline // // comment (which would end up commenting out our final "}"). // // // Root objects are never sameline. // if (name.equals("root")) // return false; // // boolean sameLine = allWritable.size() == 0 || // ( // (allWritable.size() < 4) && // !( // name.equalsIgnoreCase("trigger") || name.startsWith("action_") || // (allWritable.size() != 1 && name.equalsIgnoreCase("NOT") || (name.equalsIgnoreCase("AND") || name.equalsIgnoreCase("OR"))) // ) // ); // if (sameLine) { // for (ObjectVariable v : values) // if (v.getInlineComment().length() != 0) // return false; // for (GenericObject o : children) // if (o.getInlineComment().length() != 0) // return false; // for (GenericList l : lists) // if (l.getInlineComment().length() != 0) // return false; // } // return sameLine; // } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Static members "> private static final String[] tabs = { "", tab, tab + tab, tab + tab + tab, tab + tab + tab + tab, tab + tab + tab + tab + tab, tab + tab + tab + tab + tab + tab, tab + tab + tab + tab + tab + tab + tab, tab + tab + tab + tab + tab + tab + tab + tab, tab + tab + tab + tab + tab + tab + tab + tab + tab, }; // Could use a loop, but the array method is faster. static String getTab(final int depth) { return tabs[depth]; } static void writeTab(final BufferedWriter w, final int depth) throws IOException { w.write(tabs[depth]); // for (int i = 0; i < depth; i++) // w.write(tab); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc=" Overrides "> @Override public boolean equals(Object other) { if (other == this) return true; else if (other == null || !(other instanceof GenericObject)) return false; return equals((GenericObject)other); } @Override public int hashCode() { int hash = 5; hash = 47 * hash + (this.headComment != null ? this.headComment.hashCode() : 0); hash = 47 * hash + (this.inlineComment != null ? this.inlineComment.hashCode() : 0); hash = 47 * hash + (this.allWritable != null ? this.allWritable.hashCode() : 0); return hash; } private boolean equals(GenericObject other) { // if (parent == other.parent && // (headComment != null ? headComment.equals(other.headComment) : other.headComment == null) && // (inlineComment != null ? inlineComment.equals(other.inlineComment) : other.inlineComment == null)) { if (!name.equals(other.name)) return false; for (WritableObject obj : allWritable) { if (!other.allWritable.contains(obj)) { return false; } } for (WritableObject obj : other.allWritable) { if (!allWritable.contains(obj)) { return false; } } return true; // } else { // return false; // } } /** * Creates and returns a shallow copy of this object. Note that children * are not cloned, but the lists of children are. * @return a shallow copy of this object. */ @Override public GenericObject clone() { final GenericObject retValue = new GenericObject(parent, name); retValue.children.addAll(children); retValue.lists.addAll(lists); retValue.values.addAll(values); retValue.generalComments.addAll(generalComments); retValue.allWritable.addAll(allWritable); if (headComment != null) retValue.setHeadComment(headComment.getComment()); if (inlineComment != null) retValue.setInlineComment(inlineComment.getComment()); return retValue; } public GenericObject deepClone() { final GenericObject retValue = new GenericObject(parent, name); for (WritableObject obj : allWritable) { if (obj instanceof GenericObject) { GenericObject go = ((GenericObject)obj).deepClone(); retValue.addChild(go); } else if (obj instanceof GenericList) { GenericList gl = ((GenericList)obj).clone(); retValue.addList(gl); } else if (obj instanceof ObjectVariable) { ObjectVariable ov = ((ObjectVariable)obj).clone(); retValue.values.add(ov); retValue.allWritable.add(ov); } else if (obj instanceof InlineComment) { Comment com = ((InlineComment)obj).clone(); retValue.generalComments.add(com); retValue.allWritable.add(com); } } if (headComment != null) retValue.setHeadComment(headComment.getComment()); if (inlineComment != null) retValue.setInlineComment(inlineComment.getComment()); return retValue; } public String toString(Style style) { final java.io.StringWriter sw = new java.io.StringWriter(); final BufferedWriter bw = new BufferedWriter(sw); try { this.toFileString(bw, 0, style); bw.close(); } catch (IOException ex) { // won't happen throw new Error(ex); } return sw.toString(); } @Override public String toString() { return toString(Style.DEFAULT); } public String childrenToString() { final java.io.StringWriter sw = new java.io.StringWriter(); final BufferedWriter bw = new BufferedWriter(sw); try { this.toFileString(bw); bw.close(); } catch (IOException ex) { // won't happen throw new Error(ex); } return sw.toString(); } // </editor-fold> }