Java tutorial
/* Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org> This file is part of OpenPnP. OpenPnP 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. OpenPnP 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 OpenPnP. If not, see <http://www.gnu.org/licenses/>. For more information about OpenPnP visit http://openpnp.org */ package org.openpnp.model; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import java.util.prefs.Preferences; import org.apache.commons.io.FileUtils; import org.openpnp.ConfigurationListener; import org.openpnp.spi.JobPlanner; import org.openpnp.spi.Machine; import org.openpnp.util.ResourceUtils; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; import org.simpleframework.xml.Serializer; import org.simpleframework.xml.convert.AnnotationStrategy; import org.simpleframework.xml.core.Persister; import org.simpleframework.xml.stream.Format; import org.simpleframework.xml.stream.HyphenStyle; import org.simpleframework.xml.stream.Style; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Configuration extends AbstractModelObject { private static final Logger logger = LoggerFactory.getLogger(Configuration.class); private static Configuration instance; private static final String PREF_UNITS = "Configuration.units"; private static final String PREF_UNITS_DEF = "Millimeters"; private static final String PREF_LENGTH_DISPLAY_FORMAT = "Configuration.lengthDisplayFormat"; private static final String PREF_LENGTH_DISPLAY_FORMAT_DEF = "%.3f"; private static final String PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS = "Configuration.lengthDisplayFormatWithUnits"; private static final String PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS_DEF = "%.3f%s"; private static final String PREF_VERTICAL_SCROLL_UNIT_INCREMENT = "Configuration.verticalScrollUnitIncrement"; private static final int PREF_VERTICAL_SCROLL_UNIT_INCREMENT_DEF = 16; private LinkedHashMap<String, Package> packages = new LinkedHashMap<String, Package>(); private LinkedHashMap<String, Part> parts = new LinkedHashMap<String, Part>(); private Machine machine; private LinkedHashMap<File, Board> boards = new LinkedHashMap<File, Board>(); private boolean dirty; private boolean loaded; private Set<ConfigurationListener> listeners = Collections .synchronizedSet(new HashSet<ConfigurationListener>()); private File configurationDirectory; private Preferences prefs; public static Configuration get() { if (instance == null) { throw new Error("Configuration instance not yet initialized."); } return instance; } public static synchronized void initialize(File configurationDirectory) { instance = new Configuration(configurationDirectory); instance.setLengthDisplayFormatWithUnits(PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS_DEF); } private Configuration(File configurationDirectory) { this.configurationDirectory = configurationDirectory; this.prefs = Preferences.userNodeForPackage(Configuration.class); } public LengthUnit getSystemUnits() { return LengthUnit.valueOf(prefs.get(PREF_UNITS, PREF_UNITS_DEF)); } public void setSystemUnits(LengthUnit lengthUnit) { prefs.put(PREF_UNITS, lengthUnit.name()); } public String getLengthDisplayFormat() { return prefs.get(PREF_LENGTH_DISPLAY_FORMAT, PREF_LENGTH_DISPLAY_FORMAT_DEF); } public void setLengthDisplayFormat(String format) { prefs.put(PREF_LENGTH_DISPLAY_FORMAT, format); } public String getLengthDisplayFormatWithUnits() { return prefs.get(PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS, PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS_DEF); } public void setLengthDisplayFormatWithUnits(String format) { prefs.put(PREF_LENGTH_DISPLAY_FORMAT_WITH_UNITS, format); } public int getVerticalScrollUnitIncrement() { return prefs.getInt(PREF_VERTICAL_SCROLL_UNIT_INCREMENT, PREF_VERTICAL_SCROLL_UNIT_INCREMENT_DEF); } public void setVerticalScrollUnitIncrement(int verticalScrollUnitIncrement) { prefs.putInt(PREF_VERTICAL_SCROLL_UNIT_INCREMENT, PREF_VERTICAL_SCROLL_UNIT_INCREMENT_DEF); } /** * Gets a File reference for the resources directory belonging to the * given class. The directory is guaranteed to exist. * @param forClass * @return * @throws IOException */ public File getResourceDirectory(Class forClass) throws IOException { File directory = new File(configurationDirectory, forClass.getCanonicalName()); if (!directory.exists()) { directory.mkdirs(); } return directory; } /** * Gets a File reference for the named file within the configuration * directory. forClass is used to uniquely identify the file and keep it * separate from other classes' files. * @param forClass * @param name * @return */ public File getResourceFile(Class forClass, String name) throws IOException { return new File(getResourceDirectory(forClass), name); } /** * Creates a new file with a unique name within the configuration * directory. forClass is used to uniquely identify the file within * the application and a unique name is generated within that namespace. * suffix is appended to the unique part of the filename. The result of * calling File.getName() on the returned file can be used to load the * same file in the future by calling getResourceFile(). * This method uses File.createTemporaryFile() and so the rules for that * method must be followed when calling this one. * @param forClass * @param suffix * @return * @throws IOException */ public File createResourceFile(Class forClass, String prefix, String suffix) throws IOException { File directory = new File(configurationDirectory, forClass.getCanonicalName()); if (!directory.exists()) { directory.mkdirs(); } File file = File.createTempFile(prefix, suffix, directory); return file; } public boolean isDirty() { return dirty; } public void setDirty(boolean dirty) { this.dirty = dirty; } public void addListener(ConfigurationListener listener) { listeners.add(listener); if (loaded) { try { listener.configurationLoaded(this); listener.configurationComplete(this); } catch (Exception e) { // TODO: Need to find a way to raise this to the GUI throw new Error(e); } } } public void removeListener(ConfigurationListener listener) { listeners.remove(listener); } public void load() throws Exception { boolean forceSave = false; boolean overrideUserConfig = Boolean.getBoolean("overrideUserConfig"); try { File file = new File(configurationDirectory, "packages.xml"); if (overrideUserConfig || !file.exists()) { logger.info("No packages.xml found in configuration directory, loading defaults."); file = File.createTempFile("packages", "xml"); FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/packages.xml"), file); forceSave = true; } loadPackages(file); } catch (Exception e) { String message = e.getMessage(); if (e.getCause() != null && e.getCause().getMessage() != null) { message = e.getCause().getMessage(); } throw new Exception("Error while reading packages.xml (" + message + ")", e); } try { File file = new File(configurationDirectory, "parts.xml"); if (overrideUserConfig || !file.exists()) { logger.info("No parts.xml found in configuration directory, loading defaults."); file = File.createTempFile("parts", "xml"); FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/parts.xml"), file); forceSave = true; } loadParts(file); } catch (Exception e) { String message = e.getMessage(); if (e.getCause() != null && e.getCause().getMessage() != null) { message = e.getCause().getMessage(); } throw new Exception("Error while reading parts.xml (" + message + ")", e); } try { File file = new File(configurationDirectory, "machine.xml"); if (overrideUserConfig || !file.exists()) { logger.info("No machine.xml found in configuration directory, loading defaults."); file = File.createTempFile("machine", "xml"); FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/machine.xml"), file); forceSave = true; } loadMachine(file); } catch (Exception e) { String message = e.getMessage(); if (e.getCause() != null && e.getCause().getMessage() != null) { message = e.getCause().getMessage(); } throw new Exception("Error while reading machine.xml (" + message + ")", e); } loaded = true; for (ConfigurationListener listener : listeners) { listener.configurationLoaded(this); } if (forceSave) { logger.info("Defaults were loaded. Saving to configuration directory."); configurationDirectory.mkdirs(); save(); } for (ConfigurationListener listener : listeners) { listener.configurationComplete(this); } } public void save() throws Exception { try { saveMachine(new File(configurationDirectory, "machine.xml")); } catch (Exception e) { throw new Exception("Error while saving machine.xml (" + e.getMessage() + ")", e); } try { savePackages(new File(configurationDirectory, "packages.xml")); } catch (Exception e) { throw new Exception("Error while saving packages.xml (" + e.getMessage() + ")", e); } try { saveParts(new File(configurationDirectory, "parts.xml")); } catch (Exception e) { throw new Exception("Error while saving parts.xml (" + e.getMessage() + ")", e); } dirty = false; } public Package getPackage(String id) { if (id == null) { return null; } return packages.get(id.toUpperCase()); } public List<Package> getPackages() { return Collections.unmodifiableList(new ArrayList<Package>(packages.values())); } public void addPackage(Package pkg) { if (null == pkg.getId()) { throw new Error("Package with null Id cannot be added to Configuration."); } packages.put(pkg.getId().toUpperCase(), pkg); dirty = true; firePropertyChange("packages", null, packages); } public Part getPart(String id) { if (id == null) { return null; } return parts.get(id.toUpperCase()); } public List<Part> getParts() { return Collections.unmodifiableList(new ArrayList<Part>(parts.values())); } public void addPart(Part part) { if (null == part.getId()) { throw new Error("Part with null Id cannot be added to Configuration."); } parts.put(part.getId().toUpperCase(), part); dirty = true; firePropertyChange("parts", null, parts); } public List<Board> getBoards() { return Collections.unmodifiableList(new ArrayList<Board>(boards.values())); } public Machine getMachine() { return machine; } public Board getBoard(File file) throws Exception { if (!file.exists()) { Board board = new Board(file); board.setName(file.getName()); Serializer serializer = createSerializer(); serializer.write(board, file); } file = file.getCanonicalFile(); if (boards.containsKey(file)) { return boards.get(file); } Board board = loadBoard(file); boards.put(file, board); firePropertyChange("boards", null, boards); return board; } private void loadMachine(File file) throws Exception { Serializer serializer = createSerializer(); MachineConfigurationHolder holder = serializer.read(MachineConfigurationHolder.class, file); machine = holder.machine; } private void saveMachine(File file) throws Exception { MachineConfigurationHolder holder = new MachineConfigurationHolder(); holder.machine = machine; Serializer serializer = createSerializer(); serializer.write(holder, new ByteArrayOutputStream()); serializer.write(holder, file); } private void loadPackages(File file) throws Exception { Serializer serializer = createSerializer(); PackagesConfigurationHolder holder = serializer.read(PackagesConfigurationHolder.class, file); for (Package pkg : holder.packages) { addPackage(pkg); } } private void savePackages(File file) throws Exception { Serializer serializer = createSerializer(); PackagesConfigurationHolder holder = new PackagesConfigurationHolder(); holder.packages = new ArrayList<Package>(packages.values()); serializer.write(holder, new ByteArrayOutputStream()); serializer.write(holder, file); } private void loadParts(File file) throws Exception { Serializer serializer = createSerializer(); PartsConfigurationHolder holder = serializer.read(PartsConfigurationHolder.class, file); for (Part part : holder.parts) { addPart(part); } } private void saveParts(File file) throws Exception { Serializer serializer = createSerializer(); PartsConfigurationHolder holder = new PartsConfigurationHolder(); holder.parts = new ArrayList<Part>(parts.values()); serializer.write(holder, new ByteArrayOutputStream()); serializer.write(holder, file); } public Job loadJob(File file) throws Exception { Serializer serializer = createSerializer(); Job job = serializer.read(Job.class, file); job.setFile(file); // Once the Job is loaded we need to resolve any Boards that it // references. for (BoardLocation boardLocation : job.getBoardLocations()) { String boardFilename = boardLocation.getBoardFile(); // First see if we can find the board at the given filename // If the filename is not absolute this will be relative // to the working directory File boardFile = new File(boardFilename); if (!boardFile.exists()) { // If that fails, see if we can find it relative to the // directory the job was in boardFile = new File(file.getParentFile(), boardFilename); } if (!boardFile.exists()) { throw new Exception("Board file not found: " + boardFilename); } Board board = getBoard(boardFile); boardLocation.setBoard(board); } job.setDirty(false); return job; } public void saveJob(Job job, File file) throws Exception { Serializer serializer = createSerializer(); Set<Board> boards = new HashSet<Board>(); // Fix the paths to any boards in the Job for (BoardLocation boardLocation : job.getBoardLocations()) { Board board = boardLocation.getBoard(); boards.add(board); try { String relativePath = ResourceUtils.getRelativePath(board.getFile().getAbsolutePath(), file.getAbsolutePath(), File.separator); boardLocation.setBoardFile(relativePath); } catch (ResourceUtils.PathResolutionException ex) { boardLocation.setBoardFile(board.getFile().getAbsolutePath()); } } // Save the job serializer.write(job, new ByteArrayOutputStream()); serializer.write(job, file); job.setFile(file); job.setDirty(false); } public void saveBoard(Board board) throws Exception { Serializer serializer = createSerializer(); serializer.write(board, new ByteArrayOutputStream()); serializer.write(board, board.getFile()); board.setDirty(false); } private Board loadBoard(File file) throws Exception { Serializer serializer = createSerializer(); Board board = serializer.read(Board.class, file); board.setFile(file); board.setDirty(false); return board; } public static Serializer createSerializer() { Style style = new HyphenStyle(); Format format = new Format(style); AnnotationStrategy strategy = new AnnotationStrategy(); Serializer serializer = new Persister(strategy, format); return serializer; } /** * Used to provide a fixed root for the Machine when serializing. */ @Root(name = "openpnp-machine") public static class MachineConfigurationHolder { @Element private Machine machine; } /** * Used to provide a fixed root for the Packages when serializing. */ @Root(name = "openpnp-packages") public static class PackagesConfigurationHolder { @ElementList(inline = true, entry = "package", required = false) private ArrayList<Package> packages = new ArrayList<Package>(); } /** * Used to provide a fixed root for the Parts when serializing. */ @Root(name = "openpnp-parts") public static class PartsConfigurationHolder { @ElementList(inline = true, entry = "part", required = false) private ArrayList<Part> parts = new ArrayList<Part>(); } }