gt: 		infoMap = new HashMap<>();
context: package com.circuits.circuitsmod.circuit;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.Serializable;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.OptionalInt;
  import java.util.stream.Stream;
  
  import com.circuits.circuitsmod.CircuitsMod;
  import com.circuits.circuitsmod.TickEvents;
  import com.circuits.circuitsmod.circuitblock.WireDirectionMapper.WireDirectionGenerator;
  import com.circuits.circuitsmod.common.FileUtils;
  import com.circuits.circuitsmod.common.Log;
  import com.circuits.circuitsmod.common.Pair;
  import com.circuits.circuitsmod.common.StringUtils;
  import com.circuits.circuitsmod.controlblock.gui.model.CircuitCell;
  import com.circuits.circuitsmod.controlblock.gui.model.CircuitDirectory;
  import com.circuits.circuitsmod.controlblock.gui.model.CircuitTreeModel;
  import com.circuits.circuitsmod.network.TypedMessage;
  import com.circuits.circuitsmod.reflective.ChipImpl;
  import com.circuits.circuitsmod.reflective.ChipInvoker;
  import com.circuits.circuitsmod.reflective.SpecializedChipImpl;
  import com.google.common.collect.Lists;
  import com.google.gson.Gson;
  
  import net.minecraft.client.Minecraft;
  import net.minecraft.client.renderer.texture.DynamicTexture;
  import net.minecraft.client.renderer.texture.TextureManager;
  import net.minecraft.nbt.CompressedStreamTools;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.util.ResourceLocation;
  import net.minecraft.world.World;
  
  public class CircuitInfoProvider {
  	
  	//These will be populated on both the client and the server
  	private static HashMap<CircuitUID, CircuitInfo> infoMap;
  	private static CircuitTreeModel circuitTree;
  	
  	//These will only be populated on the server
  	private static HashMap<CircuitUID, ChipImpl> implMap;
  	
  	//This will only be populated on the client
      private static HashMap<CircuitUID, ResourceLocation> texMap = new HashMap<>();    
      private static HashMap<SpecializedCircuitUID, SpecializedCircuitInfo> infoCache = new HashMap<>();
      //TODO: Make the above intelligently forget older entries! Maybe a WeakHashMap?
  
      
      //Information about what circuit is in what directory is stored in the following map,
      //which is also maintained in a file in the configs directory for the whole mod,
      //which will keep track of the folder name/circuitUID associations
      private static HashMap<String, CircuitUID> folderToUIDMap;
      
      public static void clearState() {
      	infoCache = new HashMap<>();
      	texMap = new HashMap<>();
      	infoMap = null;
      	circuitTree = null;
      	folderToUIDMap = null;
      	implMap = null;
      	CircuitUID.clearState();
      }
      
      
      public static class SpecializedInfoRequestFromClient implements Serializable {
  		private static final long serialVersionUID = 1L; 
  		private final SpecializedCircuitUID uid;
  		
  		public SpecializedInfoRequestFromClient(SpecializedCircuitUID uid) {
  			this.uid = uid;
  		}
  		
  		public static void handle(SpecializedInfoRequestFromClient req, World worldIn) {
  			ensureServerModelInit();
  			createSpecializedInfoFor(req.uid);
  			Pair<SpecializedCircuitUID, SpecializedCircuitInfo> infoToSend = Pair.of(req.uid, infoCache.get(req.uid));
  			CircuitsMod.network.sendToAll(new TypedMessage(new SpecializedInfoResponseFromServer(infoToSend)));
  		}
      }
      
      public static class SpecializedInfoResponseFromServer implements Serializable {
  		private static final long serialVersionUID = 1L;
  		private final Pair<SpecializedCircuitUID, SpecializedCircuitInfo> mapping;
  		
  		public SpecializedInfoResponseFromServer(Pair<SpecializedCircuitUID, SpecializedCircuitInfo> mapping) {
  			this.mapping = mapping;
  		}
  		
  		public static void handle(SpecializedInfoResponseFromServer req, World worldIn) {
  			CircuitInfoProvider.infoCache.put(req.mapping.first(), req.mapping.second());
  		}
      	
      }
      
      public static class ClientModelContainer implements Serializable {
  		private static final long serialVersionUID = 1L;
  		
  		public HashMap<CircuitUID, CircuitInfo> infoMap;
      	public CircuitTreeModel circuitTree;
      	
      	public ClientModelContainer(HashMap<CircuitUID, CircuitInfo> infoMap, CircuitTreeModel circuitTree) {
      		this.infoMap = infoMap;
      		this.circuitTree = circuitTree;
      	}
      }
      
      public static class ModelRequestFromClient implements Serializable {
  		private static final long serialVersionUID = 1L; 
  		public static void handle(ModelRequestFromClient req, World worldIn) {
  			ensureServerModelInit();
  			CircuitsMod.network.sendToAll(new TypedMessage(new ClientModelContainer(infoMap, circuitTree)));
  		}
      }
      
      public static class ModelResponseFromServer implements Serializable {
  		private static final long serialVersionUID = 1L;
  		private ClientModelContainer modelContainer;
      	public ModelResponseFromServer(ClientModelContainer modelContainer) {
      		this.modelContainer = modelContainer;
      	}
  		public static void handle(ModelResponseFromServer response, World worldIn) {
  			CircuitInfoProvider.clearState();
  			CircuitInfoProvider.infoMap = response.modelContainer.infoMap;
  			CircuitInfoProvider.circuitTree = response.modelContainer.circuitTree;
  		}
      }
      
      private static File getUIDMapFile() {
      	return new File(FileUtils.getWorldSaveDir().getPath() + "/uidmap.nbt");
      }
      
      public static Optional<CircuitUID> getUIDFromFolderName(String name) {
      	for (Map.Entry<String, CircuitUID> entry : folderToUIDMap.entrySet()) {
      		if (entry.getKey().equalsIgnoreCase(name)) {
      			return Optional.of(entry.getValue());
      		}
      	}
      	return Optional.empty();
      }
      
      private static void loadUIDMapDefaults() {
      	/*
      	 * And: 0
      	 * Splitter : 2
      	 * Combiner : 3
      	 * Emitter: 7
      	 * PulseLengthener : 9
      	 * AnalogToDigital : 11
      	 * Inverter : 13
      	 * Demultiplexer : 15
      	 * NBitDLatch : 17
      	 * ABBA : 19
      	 * Input : 21
      	 * Or : 23
      	 */
      	String[] toRegister = {"And", "Xor", "Splitter", "Combiner", "Nor", "Nand",
      			               "HalfAdder", "Emitter", "RisingEdgeDetector", "PulseLengthener",
      			               "DigitalToAnalog", "AnalogToDigital", "Clock", "Inverter",
      			               "Multiplexer", "Demultiplexer", "FullAdder", "NBitDLatch", "Implies", "ABBA", "Dummy",
      			               "Input", "Output", "Or", "InputBitSelect", "OutputBitSelect", "Delay"};
      	
      	for (int i = 0; i < toRegister.length; i++) {
      		folderToUIDMap.put(toRegister[i], CircuitUID.fromInteger(i));
      	}
      }
      
      public static CircuitTreeModel getCircuitListModel() {
      	return circuitTree;
      }
      
      private static Optional<HashMap<String, CircuitUID>> uidMapFromFile(File file) {
      	try {
      		NBTTagCompound cpd = CompressedStreamTools.read(file);
      		HashMap<String, CircuitUID> result = new HashMap<>();
      		for (String key : cpd.getKeySet()) {
      			Integer value = cpd.getInteger(key);
      			result.put(key, CircuitUID.fromInteger(value));
      		}
      		return Optional.of(result);
      	}
      	catch (IOException e) {
      		return Optional.empty();
      	}
      }
      
      private static void uidMapToFile(File file, HashMap<String, CircuitUID> uidMap) {
      	try {
      		NBTTagCompound cpd = new NBTTagCompound();
      		for (String key : uidMap.keySet()) {
      			cpd.setInteger(key, uidMap.get(key).toInteger());
      		}
      		CompressedStreamTools.write(cpd, file);
      	}
      	catch (IOException e) {
      		Log.internalError("Failed to write uidMap!");
      	}
      }
      
      public static void loadUIDMapFromFile() {
      	folderToUIDMap = new HashMap<String, CircuitUID>();
      	Optional<HashMap<String, CircuitUID>> uidMap = Optional.empty();
      	if (getUIDMapFile().exists()) {
      		uidMap = uidMapFromFile(getUIDMapFile());
      	}
      	if (uidMap.isPresent()) {
      		folderToUIDMap = uidMap.get();
      	}
      	//No matter what, make sure that the defaults are always in the right place
      	loadUIDMapDefaults();
      	//After loading it into the map, make sure that lastID of circuitUID is set right
      	OptionalInt maxId = folderToUIDMap.values().stream().mapToInt((uid) -> uid.toInteger()).max();
      	CircuitUID.bumpLastUID(maxId.getAsInt());
      	saveUIDMapToFile();
      }
      
      public static void saveUIDMapToFile() {
      	uidMapToFile(getUIDMapFile(), folderToUIDMap);
      }
      
      private static Runnable clientModelInitRunnable = () -> {
      	CircuitsMod.network.sendToServer(new TypedMessage(new ModelRequestFromClient()));
      };
  
  	public static void ensureClientModelInit() {
  		if (isClientModelInit()) {
  			return;
  		}
  		TickEvents.instance().addImmediateAction(clientModelInitRunnable);
  	}
  	
  	public static boolean isClientModelInit() {
  		return infoMap != null;
  	}
  	
  	private static class SpecializedInfoRequestRunnable implements Runnable {
  		private SpecializedCircuitUID uid;
  		public SpecializedInfoRequestRunnable(SpecializedCircuitUID uid) {
  			this.uid = uid;
  		}
  		@Override
  		public void run() {
  			CircuitsMod.network.sendToServer(new TypedMessage(uid));
  		}
  		@Override
  		public int hashCode() {
  			return uid.hashCode();
  		}
  		@Override
  		public boolean equals(Object other) {
  			if (other instanceof SpecializedInfoRequestRunnable) {
  				return this.uid.equals(((SpecializedInfoRequestRunnable) other).uid);
  			}
  			return false;
  		}
  		
  	}
  	
  	public static void requestSpecializedClientInfoFor(SpecializedCircuitUID uid) {
  		if (uid != null) {
  			TickEvents.instance().addImmediateAction(new SpecializedInfoRequestRunnable(uid));
  		}
  	}
  	
  	private static CircuitUID getUIDForDir(File dir) {
  		try{
  			if (folderToUIDMap == null) {
  				loadUIDMapFromFile();
  			}
  			
  			CircuitUID result = folderToUIDMap.get(dir.getName());
  			if (result == null) {
  				//In this case, we need to generate a new UID for the given folder name!
  				result = CircuitUID.getNextUID();
  				folderToUIDMap.put(dir.getName(), result);
  				saveUIDMapToFile();
  			}
  			return result;
  		} catch (Exception e) {
  			return null;
  		}
  	}
  	
  	/**
  	 * Copies the default circuits from the mod jar to the mod-global config/circuits folder
  	 */
  	public static void copyDefaultCircuitsFromJar() {
  		ResourceLocation jarZip = new ResourceLocation(CircuitsMod.MODID, "circuits/defaults.zip");
  		File defaultCircuitsZip = new File(FileUtils.getConfigRootDir().toString() + "/defaultCircuits.zip");
  		FileUtils.copyResourceToFile(jarZip, defaultCircuitsZip);
  		FileUtils.unzip(defaultCircuitsZip, FileUtils.getGlobalCircuitDefinitionsDir());
  	}
      
  	public static void ensureServerModelInit() { 
  		if (implMap != null) {
  			return;
  		}
  		File globalCircuitsDir = FileUtils.getGlobalCircuitDefinitionsDir();
  		
  		if (!globalCircuitsDir.exists()) {
  			copyDefaultCircuitsFromJar();
  			if (!globalCircuitsDir.exists()) {
  				//Fallback, leave 'em with an empty directory
  				globalCircuitsDir.mkdirs();
  			}
  		}
  		//Okay, the above was just for initializing the mod-global circuit definitions directory.
  		//Now, we copy its contents into the world-specific circuit definitions directory,
  		//with the config-global settings taking precedence
  		File circuitsDir = FileUtils.getWorldCircuitDefinitionsDir();
  		
  		if (!FileUtils.getWorldCustomCircuitsDir().exists()) {
  			FileUtils.getWorldCustomCircuitsDir().mkdirs();
  		}
  		
  		try {
  			org.apache.commons.io.FileUtils.copyDirectory(globalCircuitsDir, circuitsDir, (f) -> !f.getName().startsWith("."));
  		}
  		catch (IOException e) {
  			Log.internalError("Failed to copy global circuit configs to world configs " + e.getMessage());
  		}
  		
  		
  		implMap = new HashMap<>();
  