<commit_before>/*
 Copyright (c) 2006, 2011, The Cytoscape Consortium (www.cytoscape.org)

 This library is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 any later version.

 This library 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.  The software and
 documentation provided hereunder is on an "as is" basis, and the
 Institute for Systems Biology and the Whitehead Institute
 have no obligations to provide maintenance, support,
 updates, enhancements or modifications.  In no event shall the
 Institute for Systems Biology and the Whitehead Institute
 be liable to any party for direct, indirect, special,
 incidental or consequential damages, including lost profits, arising
 out of the use of this software and its documentation, even if the
 Institute for Systems Biology and the Whitehead Institute
 have been advised of the possibility of such damage.  See
 the GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation,
 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */
package org.cytoscape.io.internal.read.session;

import static org.cytoscape.io.internal.util.session.SessionUtil.APPS_FOLDER;
import static org.cytoscape.io.internal.util.session.SessionUtil.BOOKMARKS_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.CYSESSION_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.NETWORK_ROOT;
import static org.cytoscape.io.internal.util.session.SessionUtil.VIZMAP_PROPS_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.XGMML_EXT;
import static org.cytoscape.model.CyNetwork.SELECTED;
import static org.cytoscape.model.CyNetwork.DEFAULT_ATTRS;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBException;

import org.cytoscape.io.internal.read.xgmml.XGMMLNetworkReader;
import org.cytoscape.io.internal.util.ReadCache;
import org.cytoscape.io.read.CyNetworkReader;
import org.cytoscape.io.read.CyNetworkReaderManager;
import org.cytoscape.io.read.CyPropertyReader;
import org.cytoscape.io.read.CyPropertyReaderManager;
import org.cytoscape.io.read.VizmapReader;
import org.cytoscape.io.read.VizmapReaderManager;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.subnetwork.CyRootNetwork;
import org.cytoscape.model.subnetwork.CyRootNetworkManager;
import org.cytoscape.property.CyProperty;
import org.cytoscape.property.SimpleCyProperty;
import org.cytoscape.property.bookmark.Bookmarks;
import org.cytoscape.property.session.Child;
import org.cytoscape.property.session.Cysession;
import org.cytoscape.property.session.Desktop;
import org.cytoscape.property.session.Edge;
import org.cytoscape.property.session.Network;
import org.cytoscape.property.session.NetworkFrame;
import org.cytoscape.property.session.Node;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.presentation.property.MinimalVisualLexicon;
import org.cytoscape.work.TaskMonitor;

/**
 * Session reader implementation that handles the Cytoscape 2.x session format.
 */
public class Cy2SessionReaderImpl extends AbstractSessionReader {
	
	public static final String CY2_PARENT_NETWORK_COLUMN = "Cytoscape2 Parent Network";
	
	public static final String CY_PROPS_FILE = "session_cytoscape.props";
	public static final Pattern NETWORK_PATTERN = Pattern.compile(".*/(([^/]+)[.]xgmml)");
	public static final Pattern PROPERTIES_PATTERN = Pattern.compile(".*/(([^/]+)[.]xgmml)");
	public static final String IGNORED_PROPS =
			"(cytoscape|proxy|logger|render|undo|vizmapper|nodelinkouturl|edgelinkouturl)\\.[^\\.]+.*" +
			"|canonicalizeNames|defaultPluginDownloadUrl|defaultVisualStyle|defaultWebBrowser" +
			"|exportTextAsShape|Linkout\\.externalLinkName|maximizeViewOnCreate|moduleNetworkViewCreationThreshold" +
			"|nestedNetwork\\.imageScaleFactor|nestedNetworkSnapshotSize|preferredLayoutAlgorithm" +
			"|secondaryViewThreshold|showQuickStartAtStartup|viewThreshold";
	
	private final CyNetworkReaderManager networkReaderMgr;
	private final CyPropertyReaderManager propertyReaderMgr;
	private final VizmapReaderManager vizmapReaderMgr;
	private final CyRootNetworkManager rootNetworkManager;

	private final Map<String, CyNetwork> networkLookup = new HashMap<String, CyNetwork>();
	private final Map<String, CyNetworkView> networkViewLookup = new HashMap<String, CyNetworkView>();
	private Map<String, String> xgmmlEntries;

	/**
	 */
	public Cy2SessionReaderImpl(final InputStream sourceInputStream,
								final ReadCache cache,
								final CyNetworkReaderManager networkReaderMgr,
								final CyPropertyReaderManager propertyReaderMgr,
								final VizmapReaderManager vizmapReaderMgr,
								final CyRootNetworkManager rootNetworkManager) {
		super(sourceInputStream, cache);
		
		if (networkReaderMgr == null)
			throw new NullPointerException("network reader manager is null!");
		this.networkReaderMgr = networkReaderMgr;

		if (propertyReaderMgr == null)
			throw new NullPointerException("property reader manager is null!");
		this.propertyReaderMgr = propertyReaderMgr;

		if (vizmapReaderMgr == null)
			throw new NullPointerException("vizmap reader manager is null!");
		this.vizmapReaderMgr = vizmapReaderMgr;

		if (rootNetworkManager == null)
			throw new NullPointerException("root network factory is null!");
		this.rootNetworkManager = rootNetworkManager;

		xgmmlEntries = new HashMap<String, String>();
	}
	
	@Override
	protected void handleEntry(InputStream is, String entryName) throws Exception {
		if (entryName.contains("/" + APPS_FOLDER + "/")) {
			extractAppEntry(is, entryName);
		} else if (entryName.endsWith(CYSESSION_FILE)) {
			extractSessionState(is, entryName);
		} else if (entryName.endsWith(VIZMAP_PROPS_FILE)) {
			extractVizmap(is, entryName);
		} else if (entryName.endsWith(CY_PROPS_FILE)) {
			extractProperties(is, entryName);
		} else if (entryName.endsWith(XGMML_EXT)) {
			// Don't extract the network now!
			// Just save the entry path, so it can be extracted
			// after the cysession file is parsed.
			Matcher matcher = NETWORK_PATTERN.matcher(entryName);

			if (matcher.matches()) {
				String fileName = matcher.group(1);
				xgmmlEntries.put(fileName, entryName);
			}
		} else if (entryName.endsWith(BOOKMARKS_FILE)) {
			extractBookmarks(is, entryName);
		} else {
			logger.warn("Unknown entry found in session zip file!\n" + entryName);
		}
	}
	
	@Override
	protected void complete(TaskMonitor tm) throws Exception {
		if (cysession == null) {
			throw new FileNotFoundException("Cannot find the " + CYSESSION_FILE + " file.");
		}

		tm.setProgress(0.4);
		tm.setTitle("Recreate networks");
		tm.setStatusMessage("Recreating networks...");
		extractNetworks(tm);
		
		tm.setProgress(0.8);
		tm.setTitle("Process networks");
		tm.setStatusMessage("Processing networks...");
		processNetworks();
		
		super.complete(tm);
	}
	
	private void extractNetworks(TaskMonitor tm) throws JAXBException, IOException {
		// Extract the XGMML files
		Map<String, Network> netMap = new HashMap<String, Network>();

		for (Network curNet : cysession.getNetworkTree().getNetwork()) {
			netMap.put(curNet.getId(), curNet);
		}

		walkNetworkTree(netMap.get(NETWORK_ROOT), null, netMap, tm);
	}
	
	private void walkNetworkTree(final Network net, CyNetwork parent, final Map<String, Network> netMap,
			final TaskMonitor tm) {
		// Get the list of children under this root
		final List<Child> children = net.getChild();

		// Traverse using recursive call
		final int numChildren = children.size();
		Child child = null;
		Network childNet = null;
		
		for (int i = 0; i < numChildren; i++) {
			child = children.get(i);
			childNet = netMap.get(child.getId());

			String entryName = xgmmlEntries.get(childNet.getFilename());
			InputStream is = null;
			CyNetwork cy2Parent = null; // This is the original Cytoscape 2 parent.
			
			try {
				is = findEntry(entryName);
				
				if (is != null) {
					tm.setStatusMessage("Extracting network: " + entryName);
					cy2Parent = extractNetworksAndViews(is, entryName, parent, childNet.isViewAvailable());
					
					// Every 2.x network should be a child of the same root-network.
					if (parent == null)
						parent = rootNetworkManager.getRootNetwork(cy2Parent);
				} else {
					logger.error("Cannot find network file \"" + entryName + "\": ");
				}
			} catch (final Exception e) {
				final String message = "Unable to read XGMML file \"" + childNet.getFilename() + "\": "
						+ e.getMessage();
				logger.error(message, e);
			} finally {
				if (is != null) {
					try {
						is.close();
					} catch (final Exception ex) {
						logger.error("Unable to close XGMML input stream.", ex);
					}
					is = null;
				}

				// Always try to load child networks, even if the parent network is bad
				if (childNet.getChild().size() != 0)
					walkNetworkTree(childNet, cy2Parent, netMap, tm);
			}
		}
	}

	/**
	 * @param is
	 * @param entryName
	 * @param parent
	 * @param createView
	 * @return The top-level network that was extracted from the XGMML file.
	 * @throws Exception
	 */
	private CyNetwork extractNetworksAndViews(final InputStream is, final String entryName, final CyNetwork parent,
			final boolean createView) throws Exception {
		CyNetwork topNetwork = null;
		final CyNetworkReader reader = networkReaderMgr.getReader(is, entryName);

		if (parent != null) {
			if (reader instanceof XGMMLNetworkReader) {
				((XGMMLNetworkReader) reader).setParent(parent);
			} else {
				logger.error("CyNetworkReader should be an instance of XGMMLNetworkReader! "
						+ "Cannot extract network as sub-nertwork of: " + entryName);
			}
		}

		reader.run(taskMonitor);

		final CyNetwork[] netArray = reader.getNetworks();
		
		if (netArray != null && netArray.length > 0) {
			topNetwork = netArray[0];

			for (int i = 0; i < netArray.length; i++) {
				CyNetwork net = netArray[i];
				String netName = net.getRow(net).get(CyNetwork.NAME, String.class);
				networkLookup.put(netName, net);
				
				// Add parent network attribute, to preserve the network hierarchy info from Cytoscape 2.x
				if (parent != null && !(parent instanceof CyRootNetwork)) {
					CyRow row = net.getRow(net);
					
					if (row.getTable().getColumn(CY2_PARENT_NETWORK_COLUMN) == null)
						row.getTable().createColumn(CY2_PARENT_NETWORK_COLUMN, Long.class, false);
					
					row.set(CY2_PARENT_NETWORK_COLUMN, parent.getSUID());
				}

				// TODO: handle 2.x groups
//				if (i == 0) {
//					// Sub-networks (other than the base-network) generated from the same 2.x XGMML file
//					// should not be registered, because they are probably 2.x groups.
					networks.add(net);
				
					if (i == 0 && createView) {
						// Create a network view for the first network only,
						// which is supposed to be the top-level one
						CyNetworkView view = reader.buildCyNetworkView(net);
						networkViewLookup.put(netName, view);
						networkViews.add(view);
					}
//				}
			}
		}
		
		return topNetwork;
	}

	private void extractAppEntry(InputStream is, String entryName) {
		String[] items = entryName.split("/");

		if (items.length < 3) {
			// It's a directory name, not a file name
			return;
		}

		String appName = items[2];
		String fileName = items[items.length - 1];

		String tmpDir = System.getProperty("java.io.tmpdir");
		File theFile = new File(tmpDir, fileName);

		try {
			// Write input stream into tmp file
			BufferedWriter out = null;
			BufferedReader in = null;
<commit_msg>Fixes #625 : [Session IO] Session reader does not read 2.x plugin files<commit_after>/*
 Copyright (c) 2006, 2011, The Cytoscape Consortium (www.cytoscape.org)

 This library is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 any later version.

 This library 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.  The software and
 documentation provided hereunder is on an "as is" basis, and the
 Institute for Systems Biology and the Whitehead Institute
 have no obligations to provide maintenance, support,
 updates, enhancements or modifications.  In no event shall the
 Institute for Systems Biology and the Whitehead Institute
 be liable to any party for direct, indirect, special,
 incidental or consequential damages, including lost profits, arising
 out of the use of this software and its documentation, even if the
 Institute for Systems Biology and the Whitehead Institute
 have been advised of the possibility of such damage.  See
 the GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation,
 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */
package org.cytoscape.io.internal.read.session;

import static org.cytoscape.io.internal.util.session.SessionUtil.BOOKMARKS_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.CYSESSION_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.NETWORK_ROOT;
import static org.cytoscape.io.internal.util.session.SessionUtil.VIZMAP_PROPS_FILE;
import static org.cytoscape.io.internal.util.session.SessionUtil.XGMML_EXT;
import static org.cytoscape.model.CyNetwork.DEFAULT_ATTRS;
import static org.cytoscape.model.CyNetwork.SELECTED;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBException;

import org.cytoscape.io.internal.read.xgmml.XGMMLNetworkReader;
import org.cytoscape.io.internal.util.ReadCache;
import org.cytoscape.io.read.CyNetworkReader;
import org.cytoscape.io.read.CyNetworkReaderManager;
import org.cytoscape.io.read.CyPropertyReader;
import org.cytoscape.io.read.CyPropertyReaderManager;
import org.cytoscape.io.read.VizmapReader;
import org.cytoscape.io.read.VizmapReaderManager;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.subnetwork.CyRootNetwork;
import org.cytoscape.model.subnetwork.CyRootNetworkManager;
import org.cytoscape.property.CyProperty;
import org.cytoscape.property.SimpleCyProperty;
import org.cytoscape.property.bookmark.Bookmarks;
import org.cytoscape.property.session.Child;
import org.cytoscape.property.session.Cysession;
import org.cytoscape.property.session.Desktop;
import org.cytoscape.property.session.Edge;
import org.cytoscape.property.session.Network;
import org.cytoscape.property.session.NetworkFrame;
import org.cytoscape.property.session.Node;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.presentation.property.MinimalVisualLexicon;
import org.cytoscape.work.TaskMonitor;

/**
 * Session reader implementation that handles the Cytoscape 2.x session format.
 */
public class Cy2SessionReaderImpl extends AbstractSessionReader {
	
	public static final String CY2_PARENT_NETWORK_COLUMN = "Cytoscape2 Parent Network";
	
	public static final String CY_PROPS_FILE = "session_cytoscape.props";
	public static final Pattern NETWORK_PATTERN = Pattern.compile(".*/(([^/]+)[.]xgmml)");
	public static final Pattern PROPERTIES_PATTERN = Pattern.compile(".*/(([^/]+)[.]xgmml)");
	public static final String IGNORED_PROPS =
			"(cytoscape|proxy|logger|render|undo|vizmapper|nodelinkouturl|edgelinkouturl)\\.[^\\.]+.*" +
			"|canonicalizeNames|defaultPluginDownloadUrl|defaultVisualStyle|defaultWebBrowser" +
			"|exportTextAsShape|Linkout\\.externalLinkName|maximizeViewOnCreate|moduleNetworkViewCreationThreshold" +
			"|nestedNetwork\\.imageScaleFactor|nestedNetworkSnapshotSize|preferredLayoutAlgorithm" +
			"|secondaryViewThreshold|showQuickStartAtStartup|viewThreshold";
	public static final String PLUGINS_FOLDER = "plugins/";
	
	private final CyNetworkReaderManager networkReaderMgr;
	private final CyPropertyReaderManager propertyReaderMgr;
	private final VizmapReaderManager vizmapReaderMgr;
	private final CyRootNetworkManager rootNetworkManager;

	private final Map<String, CyNetwork> networkLookup = new HashMap<String, CyNetwork>();
	private final Map<String, CyNetworkView> networkViewLookup = new HashMap<String, CyNetworkView>();
	private Map<String, String> xgmmlEntries;

	/**
	 */
	public Cy2SessionReaderImpl(final InputStream sourceInputStream,
								final ReadCache cache,
								final CyNetworkReaderManager networkReaderMgr,
								final CyPropertyReaderManager propertyReaderMgr,
								final VizmapReaderManager vizmapReaderMgr,
								final CyRootNetworkManager rootNetworkManager) {
		super(sourceInputStream, cache);
		
		if (networkReaderMgr == null)
			throw new NullPointerException("network reader manager is null!");
		this.networkReaderMgr = networkReaderMgr;

		if (propertyReaderMgr == null)
			throw new NullPointerException("property reader manager is null!");
		this.propertyReaderMgr = propertyReaderMgr;

		if (vizmapReaderMgr == null)
			throw new NullPointerException("vizmap reader manager is null!");
		this.vizmapReaderMgr = vizmapReaderMgr;

		if (rootNetworkManager == null)
			throw new NullPointerException("root network factory is null!");
		this.rootNetworkManager = rootNetworkManager;

		xgmmlEntries = new HashMap<String, String>();
	}
	
	@Override
	protected void handleEntry(InputStream is, String entryName) throws Exception {
		if (entryName.contains("/" + PLUGINS_FOLDER)) {
			extractPluginEntry(is, entryName);
		} else if (entryName.endsWith(CYSESSION_FILE)) {
			extractSessionState(is, entryName);
		} else if (entryName.endsWith(VIZMAP_PROPS_FILE)) {
			extractVizmap(is, entryName);
		} else if (entryName.endsWith(CY_PROPS_FILE)) {
			extractProperties(is, entryName);
		} else if (entryName.endsWith(XGMML_EXT)) {
			// Don't extract the network now!
			// Just save the entry path, so it can be extracted
			// after the cysession file is parsed.
			Matcher matcher = NETWORK_PATTERN.matcher(entryName);

			if (matcher.matches()) {
				String fileName = matcher.group(1);
				xgmmlEntries.put(fileName, entryName);
			}
		} else if (entryName.endsWith(BOOKMARKS_FILE)) {
			extractBookmarks(is, entryName);
		} else {
			logger.warn("Unknown entry found in session zip file!\n" + entryName);
		}
	}
	
	@Override
	protected void complete(TaskMonitor tm) throws Exception {
		if (cysession == null) {
			throw new FileNotFoundException("Cannot find the " + CYSESSION_FILE + " file.");
		}

		tm.setProgress(0.4);
		tm.setTitle("Recreate networks");
		tm.setStatusMessage("Recreating networks...");
		extractNetworks(tm);
		
		tm.setProgress(0.8);
		tm.setTitle("Process networks");
		tm.setStatusMessage("Processing networks...");
		processNetworks();
		
		super.complete(tm);
	}
	
	private void extractNetworks(TaskMonitor tm) throws JAXBException, IOException {
		// Extract the XGMML files
		Map<String, Network> netMap = new HashMap<String, Network>();

		for (Network curNet : cysession.getNetworkTree().getNetwork()) {
			netMap.put(curNet.getId(), curNet);
		}

		walkNetworkTree(netMap.get(NETWORK_ROOT), null, netMap, tm);
	}
	
	private void walkNetworkTree(final Network net, CyNetwork parent, final Map<String, Network> netMap,
			final TaskMonitor tm) {
		// Get the list of children under this root
		final List<Child> children = net.getChild();

		// Traverse using recursive call
		final int numChildren = children.size();
		Child child = null;
		Network childNet = null;
		
		for (int i = 0; i < numChildren; i++) {
			child = children.get(i);
			childNet = netMap.get(child.getId());

			String entryName = xgmmlEntries.get(childNet.getFilename());
			InputStream is = null;
			CyNetwork cy2Parent = null; // This is the original Cytoscape 2 parent.
			
			try {
				is = findEntry(entryName);
				
				if (is != null) {
					tm.setStatusMessage("Extracting network: " + entryName);
					cy2Parent = extractNetworksAndViews(is, entryName, parent, childNet.isViewAvailable());
					
					// Every 2.x network should be a child of the same root-network.
					if (parent == null)
						parent = rootNetworkManager.getRootNetwork(cy2Parent);
				} else {
					logger.error("Cannot find network file \"" + entryName + "\": ");
				}
			} catch (final Exception e) {
				final String message = "Unable to read XGMML file \"" + childNet.getFilename() + "\": "
						+ e.getMessage();
				logger.error(message, e);
			} finally {
				if (is != null) {
					try {
						is.close();
					} catch (final Exception ex) {
						logger.error("Unable to close XGMML input stream.", ex);
					}
					is = null;
				}

				// Always try to load child networks, even if the parent network is bad
				if (childNet.getChild().size() != 0)
					walkNetworkTree(childNet, cy2Parent, netMap, tm);
			}
		}
	}

	/**
	 * @param is
	 * @param entryName
	 * @param parent
	 * @param createView
	 * @return The top-level network that was extracted from the XGMML file.
	 * @throws Exception
	 */
	private CyNetwork extractNetworksAndViews(final InputStream is, final String entryName, final CyNetwork parent,
			final boolean createView) throws Exception {
		CyNetwork topNetwork = null;
		final CyNetworkReader reader = networkReaderMgr.getReader(is, entryName);

		if (parent != null) {
			if (reader instanceof XGMMLNetworkReader) {
				((XGMMLNetworkReader) reader).setParent(parent);
			} else {
				logger.error("CyNetworkReader should be an instance of XGMMLNetworkReader! "
						+ "Cannot extract network as sub-nertwork of: " + entryName);
			}
		}

		reader.run(taskMonitor);

		final CyNetwork[] netArray = reader.getNetworks();
		
		if (netArray != null && netArray.length > 0) {
			topNetwork = netArray[0];

			for (int i = 0; i < netArray.length; i++) {
				CyNetwork net = netArray[i];
				String netName = net.getRow(net).get(CyNetwork.NAME, String.class);
				networkLookup.put(netName, net);
				
				// Add parent network attribute, to preserve the network hierarchy info from Cytoscape 2.x
				if (parent != null && !(parent instanceof CyRootNetwork)) {
					CyRow row = net.getRow(net);
					
					if (row.getTable().getColumn(CY2_PARENT_NETWORK_COLUMN) == null)
						row.getTable().createColumn(CY2_PARENT_NETWORK_COLUMN, Long.class, false);
					
					row.set(CY2_PARENT_NETWORK_COLUMN, parent.getSUID());
				}

				// TODO: handle 2.x groups
//				if (i == 0) {
//					// Sub-networks (other than the base-network) generated from the same 2.x XGMML file
//					// should not be registered, because they are probably 2.x groups.
					networks.add(net);
				
					if (i == 0 && createView) {
						// Create a network view for the first network only,
						// which is supposed to be the top-level one
						CyNetworkView view = reader.buildCyNetworkView(net);
						networkViewLookup.put(netName, view);
						networkViews.add(view);
					}
//				}
			}
		}
		
		return topNetwork;
	}

	private void extractPluginEntry(InputStream is, String entryName) {
		String[] items = entryName.split("/");

		if (items.length < 3) {
			// It's a directory name, not a file name
			return;
		}

		String appName = items[2];
		String fileName = items[items.length - 1];

		String tmpDir = System.getProperty("java.io.tmpdir");
		File theFile = new File(tmpDir, fileName);

		try {
			// Write input stream into tmp file
			BufferedWriter out = null;
			BufferedReader in = null;

{'fraction_non_alphanumeric': 0.10100929189362384, 'fraction_numerical': 0.004726049343159244, 'mean_word_length': 4.326151877133106, 'pattern_counts': {'":': 4, '<': 29, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1923683', 'n_tokens_mistral': 8497, 'n_tokens_neox': 7338, 'n_words': 2060}