package net.sourceforge.fidocadj.librarymodel; import java.util.*; import java.io.FileNotFoundException; import java.io.IOException; import net.sourceforge.fidocadj.undo.UndoActorListener; import net.sourceforge.fidocadj.circuit.model.DrawingModel; import net.sourceforge.fidocadj.globals.Globals; import net.sourceforge.fidocadj.globals.LibUtils; import net.sourceforge.fidocadj.librarymodel.event.*; import net.sourceforge.fidocadj.primitives.MacroDesc; // TODO: comment public methods // NOTE: This model has no adding macro method. /** Model class for macro operation. This file is part of FidoCadJ. FidoCadJ is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FidoCadJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FidoCadJ. If not, @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>. Copyright 2014 Kohta Ozaki */ public class LibraryModel { final private ArrayList<LibraryListener> libraryListeners; final private DrawingModel drawingModel; final private List<Library> libraries; // Bridge for existing system. private Map<String,MacroDesc> masterLibrary; private UndoActorListener undoActorListener; /** Costructor. @param drawingModel DrawingModel instance to fetch macros. */ public LibraryModel(DrawingModel drawingModel) { this.drawingModel = drawingModel; libraryListeners = new ArrayList<LibraryListener>(); libraries = new ArrayList<Library>(); updateLibraries(); } /** Adds LibraryListener. @param listener LibraryLisner. */ public void addLibraryListener(LibraryListener listener) { libraryListeners.add(listener); } /** Removes LibraryListener. @param listener LibraryListener. */ public void removeLibraryListener(LibraryListener listener) { libraryListeners.remove(listener); } /** Removes category from library. Notices LibraryListeners after removed. @param category Category to remove. @throws IllegalLibraryAccessException If access standard library. */ public void remove(Category category) throws IllegalLibraryAccessException { Library parentLibrary; if(category==null) { return; } parentLibrary = category.getParentLibrary(); if(parentLibrary.isStdLib()) { throw new IllegalLibraryAccessException( "A category in standard library can't be removed."); } parentLibrary.removeCategory(category); synchronizeMasterLibrary(); save(); saveLibraryState(); fireRemoved(parentLibrary,category); } /** Removes library and deletes file. Notices LibraryListeners after removed. @param library Library to remove. @throws IllegalLibraryAccessException If access standard library. */ public void remove(Library library) throws IllegalLibraryAccessException { // NOTE: We must consider this method contains deleting file. if(library==null) { return; } if(library.isStdLib()) { throw new IllegalLibraryAccessException( "A standard library can't be removed."); } libraries.remove(library); synchronizeMasterLibrary(); try{ LibUtils.deleteLib(library.getFileName()); } catch (FileNotFoundException e){ System.out.println("library not found:"+library.getFileName()); } catch (IOException e) { System.out.println("Exception: "+e); } saveLibraryState(); fireRemoved(null,library); } /** Removes macro from library. Notices LibraryListeners after removed. @param macro MacroDesc to remove. @throws IllegalLibraryAccessException If access standard library. */ public void remove(MacroDesc macro) throws IllegalLibraryAccessException { Category category; if(macro==null) { return; } category = (Category)getParentNode(macro); if(category==null) { throw new IllegalLibraryAccessException("It's a wondering macro."); } if(category.getParentLibrary().isStdLib()) { throw new IllegalLibraryAccessException( "A standard library can't be removed."); } category.removeMacro(macro); synchronizeMasterLibrary(); save(); saveLibraryState(); fireRemoved(category,macro); } /** Renames macro. Notices LibraryListeners after renamed. @param macro MacroDesc to rename. @param newName New macro name. @throws IllegalLibraryAccessException If access standard library. @throws IllegalNameException If new name is invalid. */ public void rename(MacroDesc macro,String newName) throws IllegalNameException, IllegalLibraryAccessException { if(macro==null) { return; } String oldName = macro.name; //validation if(newName==null || newName.length()==0) { throw new IllegalNameException("Name length must not be zero."); } if(isStdLib(macro)) { throw new IllegalLibraryAccessException( "A macro in standard library can't be renamed."); } // TODO:validation // macro.isValidName(newName); macro.name = newName; save(); saveLibraryState(); fireRenamed(getParentNode(macro),macro,oldName); } /** Renames category. Notices LibraryListeners after renamed. @param category Category to rename. @param newName New category name. @throws IllegalLibraryAccessException If access standard library. @throws IllegalNameException If new name is invalid. */ public void rename(Category category,String newName) throws IllegalNameException,IllegalLibraryAccessException { String oldName = category.getName(); //validation if(newName==null || newName.length()==0) { throw new IllegalNameException("Name length must not be zero."); } if(category.getParentLibrary().isStdLib()) { throw new IllegalLibraryAccessException( "A category in standard library can't be renamed."); } if(!Category.isValidName(newName)) { throw new IllegalNameException("invalid name"); } category.setName(newName); synchronizeMacros(category.getParentLibrary()); save(); saveLibraryState(); fireRenamed(getParentNode(category),category,oldName); } /** Renames library. Notices LibraryListeners after renamed. @param library Library to rename. @param newName New library name. @throws IllegalLibraryAccessException If access standard library. @throws IllegalNameException If new name is invalid. */ public void rename(Library library,String newName) throws IllegalNameException,IllegalLibraryAccessException { String oldName = library.getName(); //validation if(newName==null || newName.length()==0) { throw new IllegalNameException("Name length must not be zero."); } if(library.isStdLib()) { throw new IllegalLibraryAccessException( "A standard library can't be renamed."); } if(!Library.isValidName(newName)) { throw new IllegalNameException("invalid name"); } library.setName(newName); synchronizeMacros(library); synchronizeMasterLibrary(); save(); saveLibraryState(); fireRenamed(null,library,oldName); } /** Copies macro into category. Notices LibraryListeners after copied. @param macro target macro. @param destCategory destination category. */ public void copy(MacroDesc macro, Category destCategory) { //TODO: Standard library check. MacroDesc newMacro; System.out.println("copy:"+macro+destCategory); newMacro = copyMacro(macro,destCategory); synchronizeMacros(destCategory.getParentLibrary()); synchronizeMasterLibrary(); save(); saveLibraryState(); fireAdded(destCategory,newMacro); } /** Utility function. */ private MacroDesc copyMacro(MacroDesc macro, Category destCategory) { MacroDesc newMacro; String newPlainKey; int retry; if(macro==null || destCategory==null) { return null; } newMacro = cloneMacro(macro); newPlainKey = createRandomMacroKey(); for(retry=20; 0<retry; retry--) { if(!destCategory.getParentLibrary().containsMacroKey(newPlainKey)) { break; } } if(retry<0) { throw new RuntimeException("Key generation failed."); } newMacro.key = createMacroKey(destCategory.getParentLibrary(). getFileName(),newPlainKey); destCategory.addMacro(newMacro); return newMacro; } /** Copies category into library. Notices LibraryListeners after copied. @param category target category. @param destLibrary destination library. */ public void copy(Category category, Library destLibrary) { //TODO: Standard library check. Category newCategory; if(category==null || destLibrary==null) { return; } newCategory = new Category(category.getName(), category.getParentLibrary(), false); for(MacroDesc macro:category.getAllMacros()) { copyMacro(macro,newCategory); } destLibrary.addCategory(newCategory); synchronizeMacros(destLibrary); synchronizeMasterLibrary(); save(); saveLibraryState(); fireAdded(destLibrary, newCategory); } /** Changes macro key. Notices LibraryListeners after changed. @param macro MacroDesc to change key. @param newKey New macro key without library prefix. @throws IllegalLibraryAccessException If access standard library. @throws IllegalKeyException If new key is invalid. */ public void changeKey(MacroDesc macro,String newKey) throws IllegalKeyException,IllegalLibraryAccessException { String oldKey; Category category; if(macro==null || newKey.length()==0) { throw new IllegalKeyException("Name length must not be zero."); } if(isStdLib(macro)) { throw new IllegalLibraryAccessException( "A macro in standard library can't be renamed."); } // key validation // key exists check category = (Category)getParentNode(macro); if(category.getParentLibrary().containsMacroKey(newKey)) { throw new IllegalKeyException("New key already exists."); } oldKey = getPlainMacroKey(macro); macro.key = createMacroKey(macro.filename,newKey); save(); saveLibraryState(); fireKeyChanged(getParentNode(macro),macro,oldKey); } /** Utility function. */ private MacroDesc cloneMacro(MacroDesc macro) { MacroDesc newMacro = new MacroDesc(macro.key, macro.name, macro.description, macro.category, macro.library, macro.filename); return newMacro; } /** Returns macro key without library prefix. @param macro macro. @return String plain key. */ public static String getPlainMacroKey(MacroDesc macro) { String[] parted; if(macro==null) { return null; } parted = macro.key.split("\\."); if(1<parted.length) { return parted[1]; } else { return parted[0]; } } /** Returns new macro key. @return String plain key. */ public static String createRandomMacroKey() { long t=System.nanoTime(); long h=0; for(int i=0; t>0; ++i) { t>>=i*8; h^=t & 0xFF; } return String.valueOf(h); } /** Returns identifiable macro key. @param fileName filename of library. @param key plain key. @return String key. */ public static String createMacroKey(String fileName,String key) { String macroKey = fileName+"."+key; return macroKey.toLowerCase(new Locale("en")); } /** Synchronizes MacroDesc's properties with library. */ private void synchronizeMacros(Library library) { String plainKey; if(library.isStdLib()) { return; } for(Category category:library.getAllCategories()) { for(MacroDesc m:category.getAllMacros()) { m.category = category.getName(); m.library = library.getName(); m.filename = library.getFileName(); plainKey = getPlainMacroKey(m); m.key = createMacroKey(library.getFileName(),plainKey); } } } /** Sets UndoActorListener. @param undoActorListener UndoActorListener. */ public void setUndoActorListener(UndoActorListener undoActorListener) { this.undoActorListener = undoActorListener; } /** Returns true if macro is in standard library. This method will be removed in the future. @param macro MacroDesc */ private boolean isStdLib(MacroDesc macro) { // An alternative way to see if a macro is standard or not // is to extract the prefix from the key and to see if the // prefix is "" or the one of the standard libraries. for(Library l:getAllLibraries()) { if(l.isStdLib()) { for(Category c:l.getAllCategories()) { for(MacroDesc m:c.getAllMacros()) { if(macro.equals(m)) { return true; } } } } } return false; } private void fireAdded(Object parentNode,Object addedNode) { for(LibraryListener l:libraryListeners) { l.libraryNodeAdded(new AddEvent(parentNode,addedNode)); } } private void fireRenamed(Object parentNode,Object renamedNode, String oldName) { for(LibraryListener l:libraryListeners) { l.libraryNodeRenamed(new RenameEvent(parentNode,renamedNode, oldName)); } } private void fireRemoved(Object parentNode,Object removedNode) { for(LibraryListener l:libraryListeners) { l.libraryNodeRemoved(new RemoveEvent(parentNode,removedNode)); } } private void fireKeyChanged(Object parentNode,Object changedNode, String oldKey) { for(LibraryListener l:libraryListeners) { l.libraryNodeKeyChanged(new KeyChangeEvent(parentNode,changedNode, oldKey)); } } // NOTE: This implementation is incorrect. (DB why? equals may fail?) private Object getParentNode(Object node) { for(Library l:getAllLibraries()) { if(node.equals(l)) { return null; // A library does not have any parent! } for(Category c:l.getAllCategories()) { if(node.equals(c)) { return l; } for(MacroDesc m:c.getAllMacros()) { if(node.equals(m)) { return c; } } } } return null; // Node not found } /** Returns MacroDesc map. @return Map composed of String key and MacroDesc from parser. */ public Map<String,MacroDesc> getAllMacros() { return masterLibrary; } /** Returns Libraries as list. @return List of Library objects. */ public List<Library> getAllLibraries() { return libraries; } /** Saves library to file. */ public void save() { //TODO: throw necessary exceptions. for(Library library:libraries){ try{ if(!library.isStdLib()){ LibUtils.save(masterLibrary, LibUtils.getLibPath(library.getFileName()), library.getName().trim(), library.getFileName()); } } catch (FileNotFoundException e) { } } } /** Saves library state for undo. */ public void saveLibraryState() { try { LibUtils.saveLibraryState(undoActorListener); } catch (IOException e) { System.out.println("Exception: "+e); } } /** Not implemented. */ public void undoLibrary() { // TODO: this should undo the last library operation. } /** Updates library. */ public void forceUpdate() { updateLibraries(); fireChanged(); } private void fireChanged() { for(LibraryListener l:libraryListeners) { l.libraryLoaded(); } } /** Bridges existing components. This method will be removed in the future. */ private void synchronizeMasterLibrary() { masterLibrary.clear(); for(Library library:libraries) { for(Category category:library.getAllCategories()) { for(MacroDesc macro:category.getAllMacros()) { masterLibrary.put(macro.key,macro); } } } } private void updateLibraries() { Library library; Category category; boolean catIsHidden; String key; HashMap<String,Library> tmpLibraryMap = new HashMap<String,Library>(); masterLibrary = drawingModel.getLibrary(); libraries.clear(); for(MacroDesc md:masterLibrary.values()) { cleanMacro(md); key = md.filename + "/" + md.library; if(tmpLibraryMap.containsKey(key)) { library = tmpLibraryMap.get(key); } else { library = new Library(md.library,md.filename,LibUtils.isStdLib(md)); tmpLibraryMap.put(key,library); libraries.add(library); } if(library.getCategory(md.category)==null) { catIsHidden = "hidden".equals(md.category); category = new Category(md.category,library,catIsHidden); library.addCategory(category); } else { category = library.getCategory(md.category); } category.addMacro(md); } fireChanged(); } private void cleanMacro(MacroDesc macro) { macro.name = macro.name.trim(); } /** Exception for library operation error. */ public class LibraryException extends Exception { LibraryException(String message) { super(message); } } /** Exception for an illegal name (when searching, etc...) */ public class IllegalNameException extends LibraryException { IllegalNameException(String message) { super(message); } } /** Exception for an illegal library access (i.e. non existant, most of the times). */ public class IllegalLibraryAccessException extends LibraryException { IllegalLibraryAccessException(String message) { super(message); } } /** Exception for an illegal key in a library. */ public class IllegalKeyException extends LibraryException { IllegalKeyException(String message) { super(message); } } }