/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. */ package org.opensolaris.opengrok.configuration; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.opensolaris.opengrok.util.Getopt; /** * * @author Krystof Tulinger */ public final class Groups { private interface Walker { /** * @param g group * @return true if traversing should end, false otherwise */ boolean call(Group g); } public static void main(String[] argv) { PrintStream out = System.out; File outFile = null; Configuration cfg = new Configuration(); String groupname = null; String grouppattern = null; String parent = null; boolean list = false; boolean delete = false; boolean empty = false; String match = null; Getopt getopt = new Getopt(argv, "dehi:lm:n:o:p:r:?"); try { getopt.parse(); } catch (ParseException ex) { System.err.println("Groups: " + ex.getMessage()); usage(); System.exit(1); } try { int cmd; File f; getopt.reset(); while ((cmd = getopt.getOpt()) != -1) { switch (cmd) { case 'd': delete = true; break; case 'e': empty = true; break; case 'h': usage(); System.exit(0); break; case 'i': f = new File(getopt.getOptarg()); try { cfg = Configuration.read(f); } catch (ArrayIndexOutOfBoundsException ex) { System.err.println("An error occured - this may mean that the input file is not well-formated."); System.err.println(); ex.printStackTrace(System.err); System.exit(3); } break; case 'l': list = true; break; case 'm': match = getopt.getOptarg(); break; case 'n': groupname = getopt.getOptarg(); break; case 'o': outFile = new File(getopt.getOptarg()); break; case 'p': parent = getopt.getOptarg(); break; case 'r': grouppattern = getopt.getOptarg(); break; case '?': usage(); System.exit(0); break; default: System.err.println("Internal Error - Not implemented option: " + (char) cmd); usage(); System.exit(1); break; } } } catch (FileNotFoundException ex) { System.err.println("An error occured - file does not exist"); ex.printStackTrace(System.err); System.exit(3); } catch (IOException ex) { System.err.println("An uknown error occured - the input file may be corrupted"); ex.printStackTrace(System.err); System.exit(3); } if (match != null) { // perform matching if (parent != null || groupname != null || grouppattern != null) { System.err.println("Match option should be used without parent|groupname|groupregex options"); usage(); System.exit(1); } matchGroups(System.out, cfg.getGroups(), match); } else if (empty) { // just list the groups if (parent != null || groupname != null || grouppattern != null) { System.err.println("Match option should be used without parent|groupname|groupregex options"); usage(); System.exit(1); } printOut(false, cfg, out); } else if (delete) { // perform delete if (parent != null || grouppattern != null) { System.err.println("Delete option should be used without parent|groupregex options"); usage(); System.exit(1); } if (groupname == null) { System.err.println("You must specify the group name"); usage(); System.exit(1); } deleteGroup(cfg.getGroups(), groupname); out = prepareOutput(outFile); printOut(list, cfg, out); } else if (groupname != null && grouppattern != null) { // perform insert/update. parent may be null if (!modifyGroup(cfg.getGroups(), groupname, grouppattern, parent)) { System.err.println("Parent group does not exist \"" + parent + "\""); } else { out = prepareOutput(outFile); printOut(list, cfg, out); } } else if (list) { // just list the groups if (groupname != null) { System.err.println("List option should be used without groupname options"); usage(); System.exit(1); } printOut(list, cfg, out); } else { System.err.println("Wrong combination of options. See usage."); usage(); System.exit(2); } } /** * Prints the configuration to the stream. * * @param list if true then it lists all available groups in configuration * if @param out is different than stdout it also prints the current * configuration to that stream otherwise it prints the configuration to the * @param out stream. * @param cfg configuration * @param out output stream */ private static void printOut(boolean list, Configuration cfg, PrintStream out) { if (list) { listGroups(System.out, cfg.getGroups()); if (out != System.out) { out.print(cfg.getXMLRepresentationAsString()); } } else { out.print(cfg.getXMLRepresentationAsString()); } } private static PrintStream prepareOutput(File outFile) { PrintStream out = System.out; if (outFile != null) { try { out = new PrintStream(outFile, "utf-8"); } catch (FileNotFoundException ex) { System.err.println("An error occured - file does not exist"); ex.printStackTrace(System.err); System.exit(3); } catch (UnsupportedEncodingException ex) { System.err.println("An error occured - file contains unsupported charset"); ex.printStackTrace(System.err); System.exit(3); } } return out; } /** * List groups given as a parameter. * * @param out stream * @param groups groups */ private static void listGroups(PrintStream out, Set<Group> groups) { treeTraverseGroups(groups, new Walker() { @Override public boolean call(Group g) { for (int i = 0; i < g.getFlag() * 2; i++) { out.print(" "); } out.println(g.getName() + " ~ \"" + g.getPattern() + "\""); return false; } }); } /** * Finds groups which would match the project. * * @param out stream to write the results * @param groups set of groups * @param match project name */ private static void matchGroups(PrintStream out, Set<Group> groups, String match) { Project p = new Project(match); List<Group> matched = new ArrayList<>(); linearTraverseGroups(groups, new Walker() { @Override public boolean call(Group g) { if (g.match(p)) { matched.add(g); } return false; } }); out.println(matched.size() + " group(s) match(es) the \"" + match + "\""); for (Group g : matched) { out.println(g.getName() + " \"" + g.getPattern() + "\""); } } /** * Adds a group into the xml tree. * * If group already exists, only the pattern is modified. Parent group can * be null, in that case a new group is inserted as a top level group. * * @param groups existing groups * @param groupname new group name * @param grouppattern new group pattern * @param parent parent * @return false if parent group was not found, true otherwise */ private static boolean modifyGroup(Set<Group> groups, String groupname, String grouppattern, String parent) { Group g = new Group(); g.setName(groupname); g.setPattern(grouppattern); if (updateGroup(groups, groupname, grouppattern)) { return true; } if (parent != null) { if (insertToParent(groups, parent, g)) { groups.add(g); return true; } return false; } groups.add(g); return true; } /** * Removes group from the xml tree. * * @param groups existing groups * @param groupname group to remove */ private static void deleteGroup(Set<Group> groups, String groupname) { for (Group g : groups) { if (g.getName().equals(groupname)) { groups.remove(g); groups.removeAll(g.getDescendants()); return; } } } private static boolean treeTraverseGroups(Set<Group> groups, Walker f) { LinkedList<Group> stack = new LinkedList<>(); for (Group g : groups) { g.setFlag(0); if (g.getParent() == null) { stack.add(g); } } while (!stack.isEmpty()) { Group g = stack.getFirst(); stack.removeFirst(); if (f.call(g)) { return true; } for (Group x : g.getSubgroups()) { x.setFlag(g.getFlag() + 1); stack.addFirst(x); } } return false; } private static boolean linearTraverseGroups(Set<Group> groups, Walker f) { for (Group g : groups) { if (f.call(g)) { return true; } } return false; } private static boolean insertToParent(Set<Group> groups, String parent, Group g) { return linearTraverseGroups(groups, new Walker() { @Override public boolean call(Group x) { if (x.getName().equals(parent)) { x.addGroup(g); Group tmp = x.getParent(); while (tmp != null) { tmp.addDescendant(g); tmp = tmp.getParent(); } return true; } return false; } }); } private static boolean updateGroup(Set<Group> groups, String groupname, String grouppattern) { return linearTraverseGroups(groups, new Walker() { @Override public boolean call(Group g) { if (g.getName().equals(groupname)) { g.setPattern(grouppattern); return true; } return false; } }); } private static final void usage() { System.err.println("Usage:"); System.err.println("Groups.java" + " [OPTIONS]"); System.err.println(); System.err.println("OPTIONS:"); System.err.println("Help"); System.err.println("-? print this help message"); System.err.println("-h print this help message"); System.err.println("-v verbose/debug mode"); System.err.println(); System.err.println("Input/Output"); System.err.println("-i /path/to/file input file|default is empty configuration"); System.err.println("-o /path/to/file output file|default is stdout"); System.err.println(); System.err.println("Listing"); System.err.println("-m <project name> performs matching based on given project name"); System.err.println("-l lists all available groups in input file"); System.err.println("-e creates an empty configuration or"); System.err.println(" directly outputs the input file (if given)"); System.err.println(); System.err.println("Modification"); System.err.println("-n <group name> specify group name which should be inserted|updated (requires either -r or -d option)"); System.err.println("-r <group regex> specify group regex pattern (requires -n option)"); System.err.println("-p <parent group> optional parameter for the parent group name (requires -n option)"); System.err.println("-d delete specified group (requires -n option)"); System.err.println(); System.err.println("NOTE: using modification options with -l forces the program to list all\n" + "available groups after the modification. Output to a file can still be used."); System.err.println(); System.err.println("Examples"); System.err.println("-i ~/c.xml -l # => list groups"); System.err.println("-n Abcd -r \"abcd.*\" -o ~/c.xml # => add group and print to ~/c.xml"); System.err.println("-i ~/c.xml -m abcdefg # => prints groups which would match this project description"); System.err.println("-i ~/c.xml -d -n Abcd # => deletes group Abcd"); System.err.println("-n Bcde -r \".*bcde.*\" -l # => add group and lists the result"); } }