/******************************************************************************* * Copyright 2014, * Luis Pina <luis@luispina.me>, * Michael Hicks <mwh@cs.umd.edu> * * This file is part of Rubah. * * Rubah 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. * * Rubah 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 Rubah. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package rubah.runtime; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.IOUtils; import rubah.framework.Clazz; import rubah.framework.Namespace; import rubah.runtime.classloader.FallbackLoader; import rubah.runtime.classloader.TransformerFactory; import rubah.runtime.classloader.VersionLoader; import rubah.runtime.state.Options; import rubah.tools.UpdatableJarAnalyzer; import rubah.tools.UpdatableJarAnalyzer.VersionDescriptor; import rubah.update.UpdateClass; import rubah.update.V0V0UpdateClass; public final class VersionManager { private static Namespace defaultNamespace = new Namespace(); private static VersionManager instance; public static Namespace getDefaultNamespace() { return defaultNamespace; } public static VersionManager getInstance() { if (instance == null) { instance = new VersionManager(); } return instance; } public static int runningVersionNumber; private Version runningVersion; private LinkedList<Version> versions = new LinkedList<Version>(); private Map<Version, byte[]> descriptors = new HashMap<Version, byte[]>(); private Map<Version, File> jarFiles = new HashMap<Version, File>(); private Map<Version, UpdateClass> updateClasses = new HashMap<Version, UpdateClass>(); private Set<String> currentVersionClassNames = new HashSet<>(); private Set<String> outdatedClassNames = new HashSet<>(); private VersionManager() { /* Empty */ } public void installV0V0(Options options) throws IOException { Version v0 = this.getLatestVersion(); Version v1 = this.createNextVersion(this.descriptors.get(v0)); // Compute program update v1.computeV0V0Update(options.isLazy() || options.isFullyLazy()); this.versions.addFirst(v1); this.descriptors.put(v1, this.descriptors.get(v0)); this.jarFiles.put(v1, this.jarFiles.get(v0)); this.updateClasses.put(v1, (options.getUpdateClass() == null ? new V0V0UpdateClass() : options.getUpdateClass())); this.outdatedClassNames.addAll(this.currentVersionClassNames); this.currentVersionClassNames = new HashSet<>(); for (Clazz c1 : v1.getNamespace().getDefinedClasses()) { String updatableName = v1.getUpdatableName(c1.getFqn()); this.currentVersionClassNames.add(updatableName); } } public void installVersion(Options options) throws IOException { byte[] descriptorBytes = IOUtils.toByteArray(new FileInputStream(options.getUpdateDescriptor())); Version v1 = this.createNextVersion(descriptorBytes); this.versions.addFirst(v1); this.descriptors.put(v1, descriptorBytes); this.jarFiles.put(v1, options.getJar()); this.updateClasses.put(v1, options.getUpdateClass()); // Compute program update v1.computeProgramUpdate(options.getUpdateClass(), options.isFullyLazy() || options.isLazy()); this.outdatedClassNames.addAll(this.currentVersionClassNames); this.currentVersionClassNames = new HashSet<>(); for (Clazz c1 : v1.getNamespace().getDefinedClasses()) { String updatableName = v1.getUpdatableName(c1.getFqn()); this.currentVersionClassNames.add(updatableName); } } private Version createNextVersion(byte[] descriptorBytes) throws IOException { VersionDescriptor descriptor = UpdatableJarAnalyzer.readFile(descriptorBytes, defaultNamespace); Version v1; int number = 0; if (!this.versions.isEmpty()) { number = this.versions.getFirst().getNumber() + 1; Version prevVersion = this.versions.getFirst(); // v1 = new Version(number-1, descriptor, prevVersion); v1 = new Version(number, descriptor, prevVersion); } else { v1 = new Version(number, descriptor, null); } return v1; } public Version getLatestVersion() { return this.versions.getFirst(); } public List<Version> getVersions() { return this.versions; } public byte[] getDescriptorBytes(Version v) { return this.descriptors.get(v); } public File getJarFile(Version v) { return this.jarFiles.get(v); } public byte[] getClassBytes(String className) throws IOException { return this.getClassBytes(className, this.runningVersion); } public byte[] getClassBytes(String className, TransformerFactory factory) throws IOException { return this.getClassBytes(className, this.runningVersion, factory); } public byte[] getClassBytes(String className, Version version) throws IOException { return this.getClassBytes(className, version, new TransformerFactory()); } public byte[] getClassBytes(String className, Version version, TransformerFactory factory) throws IOException { byte[] ret; if (this.currentVersionClassNames.contains(className)) { // Up-to-date class VersionLoader loader = new VersionLoader( version, this.jarFiles.get(version), factory); ret = loader.getClass(className); } else if (this.outdatedClassNames.contains(className)) { // Outdated class for (Version v : this.versions) { String originalName = v.getOriginalName(className); if (originalName == null) continue; if (v.getUpdatableName(originalName).equals(className)) { VersionLoader loader = new VersionLoader( v, this.jarFiles.get(v), factory); ret = loader.getClass(className); return ret; } } throw new Error("Unknown outdated class (should never happen): " + className); } else { // Non-updatable class ret = new FallbackLoader(this.versions.getFirst(), factory).getClass(className); } return ret; } public UpdateClass getUpdateClass(Version version) { return this.updateClasses.get(version); } public void setRunningVersion() { this.runningVersion = this.versions.getFirst(); runningVersionNumber = this.runningVersion.getNumber(); } public Version getRunningVersion() { return this.runningVersion; } }