Java tutorial
/** * Author: Andrei F. * * This file is part of the "Synapse" software and is licensed under * under the Microsoft Reference Source License (MS-RSL). * * Please see the attached LICENSE.txt for the full license. */ package com.moro.synapsemod.elements; import android.support.v4.util.ArrayMap; import android.view.View; import android.widget.LinearLayout; import com.moro.synapsemod.MainActivity; import com.moro.synapsemod.Synapse; import com.moro.synapsemod.lib.ActionNotification; import com.moro.synapsemod.lib.ActionValueClient; import com.moro.synapsemod.lib.ActionValueEvent; import com.moro.synapsemod.lib.ActionValueNotifierClient; import com.moro.synapsemod.lib.ActionValueNotifierHandler; import com.moro.synapsemod.lib.ActionValueUpdater; import com.moro.synapsemod.utils.ElementFailureException; import com.moro.synapsemod.utils.L; import com.moro.synapsemod.utils.Utils; import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; public class STreeDescriptor extends BaseElement implements ActionValueNotifierClient { private String directoryPath; private static class ObjectDescriptor { String type; JSONObject element; BaseElement instance; ObjectDescriptor(String type, JSONObject element) { this.type = type; this.element = element; } ObjectDescriptor(ObjectDescriptor copy) { this.type = copy.type; this.element = (JSONObject) copy.element.clone(); } } private final ObjectDescriptor directoryDescriptor; private final ObjectDescriptor elementDescriptor; private ArrayMap<String, ObjectDescriptor> directories; private ArrayMap<String, ObjectDescriptor> elements; private ArrayList<ObjectDescriptor> descriptorList = new ArrayList<>(); private ArrayList<String> excludes = new ArrayList<>(); private ArrayList<BaseElement> baseElements = new ArrayList<>(); private ArrayList<String> actionList = new ArrayList<>(); private ArrayList<STreeDescriptor> children = new ArrayList<>(); //private MainActivity.TabSectionFragment container = null; public STreeDescriptor(JSONObject element, LinearLayout layout, MainActivity.TabSectionFragment fragment) { super(element, layout, fragment); if (element.containsKey("path")) this.directoryPath = (String) element.get("path"); if (element.containsKey("exclude")) { JSONArray list = (JSONArray) element.get("exclude"); for (Object term : list) excludes.add((String) term); } if (element.containsKey("generic")) { JSONObject generic = (JSONObject) element.get("generic"); if (generic.containsKey("directory")) { JSONObject directory = (JSONObject) generic.get("directory"); String type = Utils.getEnclosure(directory); directory = (JSONObject) directory.get(type); this.directoryDescriptor = new ObjectDescriptor(type, directory); } else this.directoryDescriptor = null; if (generic.containsKey("element")) { JSONObject elementObject = (JSONObject) generic.get("element"); String type = Utils.getEnclosure(elementObject); elementObject = (JSONObject) elementObject.get(type); this.elementDescriptor = new ObjectDescriptor(type, elementObject); } else this.elementDescriptor = null; } else { this.directoryDescriptor = null; this.elementDescriptor = null; } if (element.containsKey("matched")) { JSONObject matched = (JSONObject) element.get("matched"); if (matched.containsKey("directories")) { JSONArray directoriesJSON = (JSONArray) matched.get("directories"); this.directories = new ArrayMap<>(); for (Object directoryEntry : directoriesJSON) { JSONObject valuePair = (JSONObject) directoryEntry; String path = Utils.getEnclosure(valuePair); JSONObject directoryObject = (JSONObject) valuePair.get(path); String type = Utils.getEnclosure(directoryObject); directoryObject = (JSONObject) directoryObject.get(type); this.directories.put(path, new ObjectDescriptor(type, directoryObject)); } } if (matched.containsKey("elements")) { JSONArray elementsJSON = (JSONArray) matched.get("elements"); this.elements = new ArrayMap<>(); for (Object elementEntry : elementsJSON) { JSONObject valuePair = (JSONObject) elementEntry; String path = Utils.getEnclosure(valuePair); JSONObject elementObject = (JSONObject) valuePair.get(path); String type = Utils.getEnclosure(elementObject); elementObject = (JSONObject) elementObject.get(type); this.elements.put(path, new ObjectDescriptor(type, elementObject)); } } } buildHierarchy(); ActionValueNotifierHandler.register(this); } private String replaceTokens(String input, File file) { return input.replace("@NAME", file.getName().replace('_', ' ')).replace("@BASENAME", file.getName()) .replace("@PATH", file.getAbsolutePath()); } private void buildHierarchy() { File[] files = new File(this.directoryPath).listFiles(); if (files != null) { for (File file : files) { if (excludes.contains(file.getName())) continue; if (file.isDirectory()) { if (this.directories != null && this.directories.containsKey(file.getName())) { descriptorList.add(this.directories.get(file.getName())); } else if (directoryDescriptor != null) { ObjectDescriptor desc = new ObjectDescriptor(directoryDescriptor); desc.element.put("title", file.getName()); if (desc.type.equals(this.getClass().getSimpleName())) desc.element.put("path", file.getAbsolutePath()); descriptorList.add(desc); } else L.e("Directory descriptor is null for " + directoryPath); } else { ObjectDescriptor desc = null; if (this.elements != null && this.elements.containsKey(file.getName())) { desc = this.elements.get(file.getName()); } else if (elementDescriptor != null) { desc = new ObjectDescriptor(elementDescriptor); if (desc.element.containsKey("title")) desc.element.put("title", replaceTokens((String) desc.element.get("title"), file)); } else L.e("File descriptor is null for " + directoryPath); if (desc != null) { if (!desc.element.containsKey("action")) desc.element.put("action", "generic" + ' ' + file.getAbsolutePath()); else desc.element.put("action", replaceTokens(((String) desc.element.get("action")), file)); descriptorList.add(desc); } } } } descriptorLoop: for (ObjectDescriptor d : descriptorList) { if (d.type.equals(this.getClass().getSimpleName())) { for (STreeDescriptor parser : this.children) if (parser.directoryPath.equals(d.element.get("path"))) { d.instance = parser; continue descriptorLoop; } try { d.instance = BaseElement.createObject(d.type, d.element, this.layout, this.fragment); this.children.add((STreeDescriptor) d.instance); continue; } catch (ElementFailureException e) { e.printStackTrace(); } } if (d.element.containsKey("action")) actionList.add((String) d.element.get("action")); } } @Override public View getView() { for (ObjectDescriptor d : descriptorList) { try { BaseElement b; if (d.type.equals(this.getClass().getSimpleName())) b = d.instance; else b = BaseElement.createObject(d.type, d.element, this.layout, this.fragment); baseElements.add(b); fragment.addElement(b); if (b instanceof ActionValueClient) ActionValueUpdater.registerPerpetual((ActionValueClient) b, this.fragment.getSectionNumber()); View v = b.getView(); if (v != null) this.layout.addView(v); } catch (ElementFailureException e) { this.layout.addView(Utils.createElementErrorView(e)); } } return null; } private void collapse() throws ElementFailureException { View v; LinearLayout p; for (BaseElement e : baseElements) { if (e instanceof ActionValueClient) { if (ActionValueUpdater.isRegistered((ActionValueClient) e)) ActionValueUpdater.removeElement((ActionValueClient) e); if (ActionValueUpdater.isPerpetual((ActionValueClient) e)) ActionValueUpdater.removePerpetual((ActionValueClient) e); } if (e instanceof ActionValueNotifierClient) { ActionValueNotifierHandler.remove((ActionValueNotifierClient) e); } /* * Trying to get a View of an child STreeDescriptor is not what we want to do here, * and will cause undefined behaviour. A collapse will logically mean an invalidation * of the whole tree, and getting a View of a child will cause a parsing of all * of its elements, which will not be able to operate normally as the parent node * will be invalid. */ if (e instanceof STreeDescriptor) continue; v = e.getView(); if (v != null) { p = (LinearLayout) v.getParent(); if (p != null) p.removeView(v); } fragment.removeElement(e); } actionList.clear(); baseElements.clear(); descriptorList.clear(); for (STreeDescriptor b : this.children) b.collapse(); this.children.clear(); } private void rebuild() { buildHierarchy(); getView(); } public ArrayList<String> getFlatActionTreeList() { ArrayList<String> f = new ArrayList<>(); f.addAll(this.actionList); for (STreeDescriptor b : this.children) f.addAll(b.getActions()); return f; } private ArrayList<String> getActions() { return this.actionList; } /** * ActionValueNotifierClient methods */ @Override public String getId() { return directoryPath; } private ArrayDeque<ActionNotification> queue = new ArrayDeque<>(); private boolean jobRunning = false; private void handleNotifications() { jobRunning = true; while (queue.size() > 0) { ActionNotification current = queue.removeFirst(); switch (current.notification) { case REFRESH: try { collapse(); rebuild(); } catch (ElementFailureException e) { e.printStackTrace(); } break; case CANCEL: try { cancelValue(); } catch (ElementFailureException e) { e.printStackTrace(); } break; case RESET: setDefaults(); break; case APPLY: try { applyValue(); } catch (ElementFailureException e) { e.printStackTrace(); } break; } } jobRunning = false; } private Runnable dequeJob = new Runnable() { @Override public void run() { handleNotifications(); } }; @Override public void onNotify(ActionValueNotifierClient source, ActionValueEvent notification) { queue.add(new ActionNotification(source, notification)); if (queue.size() == 1 && !jobRunning) Synapse.handler.post(dequeJob); } @Override public String getLiveValue() throws ElementFailureException { return null; } @Override public String getSetValue() { return null; } @Override public String getStoredValue() { return null; } @Override public void refreshValue() throws ElementFailureException { for (BaseElement b : baseElements) if (b instanceof ActionValueClient) ((ActionValueClient) b).refreshValue(); } @Override public void setDefaults() { for (BaseElement b : baseElements) if (b instanceof ActionValueClient) ((ActionValueClient) b).setDefaults(); } @Override public void applyValue() throws ElementFailureException { for (BaseElement b : baseElements) if (b instanceof ActionValueClient) ((ActionValueClient) b).applyValue(); } @Override public void cancelValue() throws ElementFailureException { for (BaseElement b : baseElements) if (b instanceof ActionValueClient) ((ActionValueClient) b).cancelValue(); } }