Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added the ability to fetch repository-logs at runtime.

Using runrepositoryfetch.bat, the user is now able to start codeswarm alternatively with a swing-frame. 

This frame asks the user for a repository location, a username and a password for the repository.
It is able to connect to any svn-repository, since it uses svnkit to fetch the repository-log.
The repository-log gets fetched and converted to the xml-format, codeswarm expects as input.
The xml-file is cached until the latestRevision of the remote repository changes.
See javadoc for detailed information.

git-svn-id: http://codeswarm.googlecode.com/svn/trunk@235 eda9c206-d64f-0410-a2f2-67f3fa0499ed
  • Loading branch information...
commit 5d0452a47c33e20f65bcf826b1ee39ff9e37e881 1 parent fb9feea
kraeusen authored
View
15 build.xml
@@ -20,8 +20,17 @@
<target name="build" depends="init" description="Compiles">
<echo>Running BUILD</echo>
<mkdir dir="${build}" />
- <javac destdir="${build}" srcdir="${src}"
-classpath="${lib}/core.jar;${lib}/xml.jar;${lib}/jogl.jar;${lib}/opengl.jar;${lib}/vecmath.jar" debug="true" deprecation="false" />
+ <path id="library.classpath">
+ <pathelement path="lib/" />
+ <pathelement location="lib/core.jar" />
+ <pathelement location="lib/xml.jar" />
+ <pathelement location="lib/jogl.jar" />
+ <pathelement location="lib/opengl.jar" />
+ <pathelement location="lib/vecmath.jar" />
+ <pathelement location="lib/svnkit.jar" />
+ <pathelement location="lib/swing-layout-1.0.3.jar" />
+ </path>
+ <javac destdir="${build}" srcdir="${src}" classpathref="library.classpath" debug="true" deprecation="false" />
<copy file="${src}/particle.png" todir="${build}" />
</target>
@@ -43,7 +52,7 @@ classpath="${lib}/core.jar;${lib}/xml.jar;${lib}/jogl.jar;${lib}/opengl.jar;${li
<target name="run" depends="jar" description="Runs the program">
<echo>Running code_swarm</echo>
- <java classname="code_swarm" fork="true">
+ <java classname="MainView" fork="true">
<jvmarg value="-Xmx1000m" />
<arg value="data/sample.config"/>
<classpath>
View
5 data/log.properties
@@ -0,0 +1,5 @@
+#see http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/LogManager.html
+.level=SEVERE
+org.codeswarm.level=FINEST
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level = ALL
View
BIN  lib/svnkit.jar
Binary file not shown
View
BIN  lib/swing-layout-1.0.3.jar
Binary file not shown
View
43 runrepositoryfetch.bat
@@ -0,0 +1,43 @@
+@ECHO OFF
+REM run.bat : code_swarm launching script
+REM need the config file as first parameter
+
+REM TODO : take care of multiple parameters
+SET params="%1"
+set default_config="data/sample.config"
+
+REM basic command line parameters check
+IF %params%=="" (
+ REM asking user for a config file
+ ECHO Specify a config file, or type
+ SET /p params="ENTER for default one [%default_config%] : "
+)
+IF %params%=="" (
+ SET params=%default_config%
+)
+
+REM help requested ?
+IF "%1"=="/?" goto help
+IF "%1"=="/H" goto help
+IF "%1"=="-h" goto help
+IF "%1"=="--help" goto help
+goto binaryCheck
+:help
+ REM if help needed, print it and exit
+ echo usage: run configfile
+ echo data/sample.config is the default config file"
+ exit
+
+:binaryCheck
+REM checking for code_swarm java binaries
+IF NOT EXIST dist\code_swarm.jar (
+ echo no code_swarm binaries !
+ echo needing to build it with 'ant' and 'javac' java-sdk
+ echo auto-trying the ant command...
+ call ant
+)
+
+REM running
+REM echo %params%
+echo code_swarm project !
+java -Xmx1000m -classpath dist/code_swarm.jar;lib/core.jar;lib/xml.jar;lib/vecmath.jar;lib/svnkit.jar;lib/swing-layout-1.0.3.jar;. MainView %params%
View
13 src/CodeSwarmConfig.java
@@ -123,7 +123,7 @@ public CodeSwarmConfig(String configFileName) throws IOException
p = new Properties( this.createDefaults() );
p.load( new FileInputStream(configFileName) );
}
-
+
private Properties createDefaults()
{
Properties def = new Properties();
@@ -155,6 +155,17 @@ public Color getBackground()
}
/**
+ * Specify the path to the Xml-input file containing the repository
+ * entries.<br />
+ * Further versions should not use input-file but an abstract view
+ * of the repository-entries.
+ * @see EventList
+ * @param filePath the path to the Xml-input file.
+ */
+ public void setInputFile(String filePath){
+ p.setProperty(INPUT_FILE_KEY, filePath);
+ }
+ /**
*
* @param key
* @param defValue
View
117 src/MainView.form
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="2"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <AuxValues>
+ <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel3" alignment="0" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="userName" pref="305" max="32767" attributes="0"/>
+ <Component id="repositoryURL" alignment="0" pref="305" max="32767" attributes="0"/>
+ <Component id="password" alignment="0" pref="305" max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ <Group type="102" alignment="1" attributes="0">
+ <Component id="clearCache" min="-2" max="-2" attributes="0"/>
+ <EmptySpace pref="242" max="32767" attributes="0"/>
+ <Component id="goButton" min="-2" max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="repositoryURL" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="userName" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="password" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel3" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace type="unrelated" max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="goButton" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="clearCache" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel1">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="RepositoryURL"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel2">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Username"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel3">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Password"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="repositoryURL">
+ </Component>
+ <Component class="javax.swing.JTextField" name="userName">
+ </Component>
+ <Component class="javax.swing.JPasswordField" name="password">
+ </Component>
+ <Component class="javax.swing.JButton" name="goButton">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="GO"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="goButtonActionPerformed"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="clearCache">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Clear Cache"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="clearCacheActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+</Form>
View
240 src/MainView.java
@@ -0,0 +1,240 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.codeswarm.repository.svn.SVNHistory;
+import java.awt.Toolkit;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.LogManager;
+import java.util.prefs.Preferences;
+import javax.swing.JRootPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+/**
+ * This is the main entry point for the application.<br />
+ * This version is a fast implementation of a user-interface.<br />
+ * Further versions should be improved and include stuff like i18n.<br />
+ * The usage of Swing-Application-Framework and Beans-Binding could help in the
+ * future.<br />
+ *
+ * NOTE: This dialog was made using Netbeans. Modifications should be done with
+ * Netbeans (at least 6.0) to reflect them to the corresponding netbeans-form.
+ * @author tpraxl
+ */
+public class MainView extends javax.swing.JFrame {
+ // This class couples SVNHistory too tightly.
+ // TODO The concrete History-Implementation should be created by a Factory
+ // depending on the URL and/or other settings.
+
+ /**
+ * initializes the Dialog and sets the dialog-values according to the
+ * user's preferences (the last values entered).
+ * @param args java arguments passed to the main method. The first parameter
+ * will be passed to {@link code_swarm}. It specifies the config-file.
+ */
+ public MainView(String[] args) {
+ this.args = args;
+ initComponents();
+ this.getRootPane().setDefaultButton(goButton);
+ Preferences p = Preferences.userNodeForPackage(MainView.class);
+ String username = p.get("username", "");
+ userName.setText(username);
+ String url = p.get("repositoryURL", "http://");
+ repositoryURL.setText(url);
+ }
+
+ @SuppressWarnings("unchecked")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ jLabel3 = new javax.swing.JLabel();
+ repositoryURL = new javax.swing.JTextField();
+ userName = new javax.swing.JTextField();
+ password = new javax.swing.JPasswordField();
+ goButton = new javax.swing.JButton();
+ clearCache = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+
+ jLabel1.setText("RepositoryURL");
+
+ jLabel2.setText("Username");
+
+ jLabel3.setText("Password");
+
+ goButton.setText("GO");
+ goButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ goButtonActionPerformed(evt);
+ }
+ });
+
+ clearCache.setText("Clear Cache");
+ clearCache.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ clearCacheActionPerformed(evt);
+ }
+ });
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jLabel1)
+ .add(jLabel2)
+ .add(jLabel3))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(userName, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)
+ .add(repositoryURL, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)
+ .add(password, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)))
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
+ .add(clearCache)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 242, Short.MAX_VALUE)
+ .add(goButton)))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(jLabel1)
+ .add(repositoryURL, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(jLabel2)
+ .add(userName, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(password, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .add(jLabel3))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(goButton)
+ .add(clearCache))
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ /**
+ * gets called when the user presses the "Go"-Button.<br />
+ * It manages fetching the repository entries and serving it to
+ * {@link code_swarm}. It starts code_swarm after fetching the repository
+ * entries.
+ * @param evt The ActionEvent from Swing
+ */
+private void goButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goButtonActionPerformed
+ Runnable run = new Runnable() {
+ public void run() {
+ goButton.setEnabled(false);
+ clearCache.setEnabled(false);
+ Preferences p = Preferences.userNodeForPackage(MainView.class);
+ String username = userName.getText();
+ String passwd = String.valueOf(password.getPassword());
+ String url = repositoryURL.getText();
+ p.put("username", username);
+ p.put("repositoryURL", url);
+ SVNHistory hist = new SVNHistory("realtime_sample");
+ hist.run(url, username, passwd);
+ try {
+ CodeSwarmConfig cfg = new CodeSwarmConfig(args[0]);
+ cfg.setInputFile(hist.getFilePath());
+ code_swarm.start(cfg);
+ dispose();
+ } catch (IOException e) {
+ System.err.println("Failed due to exception: " + e.getMessage());
+ goButton.setEnabled(true);
+ clearCache.setEnabled(true);
+ }
+ }
+ };
+ new Thread(run).start();
+}//GEN-LAST:event_goButtonActionPerformed
+
+/**
+ * gets called when the user presses the "Clear Cache" Button. It clears the
+ * version cache.
+ * @param evt the Swing ActionEvent.
+ */
+private void clearCacheActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearCacheActionPerformed
+ SVNHistory.clearCache();
+}//GEN-LAST:event_clearCacheActionPerformed
+
+ /**
+ * This is the main entry-point. It sets the native Look&Feel, creates and
+ * shows the MainView.
+ * @param args the command line arguments. The first parameter
+ * will be passed to {@link code_swarm}. It specifies the config-file.
+ */
+ public static void main(final String args[]) {
+ java.awt.EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ Toolkit.getDefaultToolkit().setDynamicLayout(true);
+ try{
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }catch(ClassNotFoundException e){
+ // not that fatal. No need to log.
+ }catch(InstantiationException e){
+ // not that fatal. No need to log.
+ }catch(IllegalAccessException e){
+ // not that fatal. No need to log.
+ }catch(UnsupportedLookAndFeelException e){
+ // not that fatal. No need to log.
+ }
+ try {
+ File f = new File("data/log.properties");
+ InputStream in = new FileInputStream(f);
+ LogManager.getLogManager().readConfiguration(in);
+ in.close();
+ } catch (IOException ex) {
+ // no problem. Standard-logging is performed (Console)
+ Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (SecurityException ex) {
+ Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ new MainView(args).setVisible(true);
+ }
+ });
+ }
+ private String[] args;
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton clearCache;
+ private javax.swing.JButton goButton;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JPasswordField password;
+ private javax.swing.JTextField repositoryURL;
+ private javax.swing.JTextField userName;
+ // End of variables declaration//GEN-END:variables
+
+}
View
13 src/code_swarm.java
@@ -1133,7 +1133,7 @@ public int compareTo(Object o) {
// init life relative vars
life = LIFE_INIT;
}
-
+
/**
* 4) shortening life.
*/
@@ -1434,4 +1434,15 @@ static public void main(String args[]) {
System.err.println("Failed due to exception: " + e.getMessage());
}
}
+ /**
+ * the alternative entry-point for code_swarm. It gets called from
+ * {@link MainView} after fetching the repository log.
+ * @param config the modified config
+ * (it's InputFile-property has been changed to reflect the
+ * fetched repository-log)
+ */
+ public static void start(CodeSwarmConfig config){
+ cfg = config;
+ PApplet.main(new String[]{"code_swarm"});
+ }
}
View
113 src/org/codeswarm/repository/RepositoryHistoryVisitor.java
@@ -0,0 +1,113 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.codeswarm.repository;
+
+/**
+ * This interface provides hooks and methods for concrete
+ * RepositoryHistoryVisitor-implementations.<br />
+ * It is not tight to concrete repositories like CVS, SVN, etc. but can be
+ * used as an API to visit arbitrary repositories.
+ * Derived classes may be of two types:<br />
+ * <dl>
+ * <dt>Repository-specific Behaviour</dt>
+ * <dd>Derived classes that implement the behaviour to visit the
+ * repository-entries of concrete repositories (like CVS, SVN).<br />
+ * These classes should be abstract.
+ * They should implement {@link #run(String, String, String)}.<br />
+ * They should not implement the other methods, but simply call them in
+ * appropriate situations, as those methods are meant to be hooks.
+ * </dd>
+ * <dt>Context-specific Behaviour</dt>
+ * <dd>These classes are not directly derived from this interface.<br />
+ * They implement the abstract Repository-specific behaviours and perform
+ * operations on the hooks being called.
+ * </dd>
+ * </dl>
+ *
+ * NOTE: The interface is work in progress and should be improved.
+ * @author tpraxl
+ */
+public interface RepositoryHistoryVisitor <E, T, R > {
+ // TODO remove userName and password from this implementation
+ // and replace it with an AuthenticationManager to be able to use arbitrary
+ // authentication-mechanisms such as key-file based authentication.
+
+ // TODO think about redesign to decouple from SVNKit.
+ // This interface was designed around SVNKit's behaviour. It's design
+ // should be changed to be more abstract.
+ /**
+ * This method gets called when the visitor shall start its operation.
+ * @param url complete repository url
+ * (with protocoll prefix, such as http://, svn://)
+ * @param name userName
+ * @param password userPassword
+ */
+ public void run(String url, String name, String password);
+
+ // HOOKS
+ // EXCEPTION HOOKS:
+ /**
+ * gets called before any connection is performed.
+ * @param url the url to connect to
+ */
+ public abstract void handleStart(String url);
+ /**
+ * An Exception has occured while trying to create a Repository driver
+ * for the specified protocol. No RepositoryDriver is available for the
+ * protocol. This exception is typically not recoverable.
+ * @param exception The original Exception
+ * @param url The url whose protocol was analyzed.
+ * @return false if you want to stop further processing, true if you want to
+ * proceed. Typically you will want to return false.
+ */
+ public abstract boolean handleCreateRepositoryException(T exception, String url);
+ /**
+ * An Exception has occured while trying to fetch the latest revision
+ * number. This exception is caused by authentication or connection failures.
+ * @param exception The original Exception
+ * @return null if you want to stop further processing, a desired Revision if you want to
+ * proceed. Typically you will want to return null.
+ */
+ public abstract R handleFetchingLatestRepositoryRevisionException(T exception);
+ /**
+ * An Exception has occured while trying to fetch the revision-log.
+ * This exception is caused by authentication or connection failures.
+ * @param exception The original Exception
+ * @param url The url to the repository that was asked for the log.
+ * @return false if you want to stop further processing, true if you want to
+ * proceed. Typically you will want to return false.
+ */
+ public abstract boolean handleCollectingLogInformationException(T exception, String url);
+ // PROCESSING HOOKS:
+ /**
+ * A revision log entry was visited. Implement code to process the entry.
+ * @param entry the entry that was visited. Usually not null.
+ */
+ public abstract void handleLogEntry(E entry);
+ /**
+ * The latest repository revision was fetched. Implement code to process the
+ * information.
+ * @param revision the latest repository revision
+ * @return false if you want to stop further processing, true if you want to
+ * proceed. Return false to stop processing for example when a cached
+ * version was found.
+ */
+ public abstract boolean handleFetchingLatestRepositoryRevision(R revision);
+ /**
+ * The process has successfully finished.
+ */
+ public abstract void finishLogEntries();
+}
View
203 src/org/codeswarm/repository/svn/AbstractSVNHistoryVisitor.java
@@ -0,0 +1,203 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+// Some parts of this code have been taken from SVNKit's example-code.
+// See https://wiki.svnkit.com/Printing_Out_Repository_History
+
+package org.codeswarm.repository.svn;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.codeswarm.repository.RepositoryHistoryVisitor;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNLogEntry;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
+import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
+import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
+import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
+import org.tmatesoft.svn.core.io.SVNRepository;
+import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+
+/**
+ * Provides access to the repository log of svn-repositories. Using SVNKit.<br />
+ * It simply visits all entries of the log and calls the corresponding hooks.<br />
+ *
+ * Derived classes must implement the hooks to perform corresponding operations.
+ * @see org.codeswarm.repository.RepositoryHistoryVisitor
+ * NOTE: The class is work in progress and should be improved.
+ * @author tpraxl
+ */
+public abstract class AbstractSVNHistoryVisitor implements RepositoryHistoryVisitor<SVNLogEntry, SVNException, Long>{
+ /**
+ * Starts the repository-log-lookup from the first to the last revision.
+ * @see #run(String, Long, Long, String, String)
+ * @param url the complete url to the repository
+ * (including the protocol (http://, svn://,...))
+ * @param name username for authentication
+ * @param password users password for authentication
+ */
+ public void run(String url, String name, String password){
+ run(url, null, null, name, password);
+ }
+ /**
+ * Starts the repository-log-lookup.
+ * @see #run(String, String, String)
+ * @param url the complete url to the repository
+ * (including the protocol (http://, svn://,...))
+ * @param pStartrevision the revision to start with
+ * @param pEndrevision the last revision to take into account.<br />
+ * NOTE: Currently this parameter is not supported.
+ * The log will always be fetched until the last revision.
+ * @param name username for authentication
+ * @param password users password for authentication
+ */
+ public void run(String url, Long pStartrevision, Long pEndrevision, String name, String password) {
+ handleStart(url);
+ long startRevision = pStartrevision!=null?pStartrevision.longValue():0;
+ long endRevision = pEndrevision!=null?pEndrevision.longValue():-1;//HEAD (the latest) revision
+ /*
+ * Initializes the library (it must be done before ever using the
+ * library itself)
+ */
+ setupLibrary();
+
+ SVNRepository repository = null;
+
+ try {
+ /*
+ * Creates an instance of SVNRepository to work with the repository.
+ * All user's requests to the repository are relative to the
+ * repository location used to create this SVNRepository.
+ * SVNURL is a wrapper for URL strings that refer to repository locations.
+ */
+ repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
+ } catch (SVNException svne) {
+ if(!handleCreateRepositoryException(svne, url)){
+ return;
+ }
+ }
+
+ /*
+ * User's authentication information (name/password) is provided via an
+ * ISVNAuthenticationManager instance. SVNWCUtil creates a default
+ * authentication manager given user's name and password.
+ *
+ * Default authentication manager first attempts to use provided user name
+ * and password and then falls back to the credentials stored in the
+ * default Subversion credentials storage that is located in Subversion
+ * configuration area. If you'd like to use provided user name and password
+ * only you may use BasicAuthenticationManager class instead of default
+ * authentication manager:
+ *
+ * authManager = new BasicAuthenticationsManager(userName, userPassword);
+ *
+ * You may also skip this point - anonymous access will be used.
+ */
+ ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(name, password);
+ repository.setAuthenticationManager(authManager);
+
+ /*
+ * Gets the latest revision number of the repository
+ */
+ try {
+ endRevision = repository.getLatestRevision();
+ if(!handleFetchingLatestRepositoryRevision(endRevision)){
+ return;
+ }
+ } catch (SVNException svne) {
+ Long revision = handleFetchingLatestRepositoryRevisionException(svne);
+ if(revision==null){
+ return;
+ }else{
+ endRevision = revision;
+ }
+ }
+
+ Collection logEntries = null;
+ try {
+ /*
+ * Collects SVNLogEntry objects for all revisions in the range
+ * defined by its start and end points [startRevision, endRevision].
+ * For each revision commit information is represented by
+ * SVNLogEntry.
+ *
+ * the 1st parameter (targetPaths - an array of path strings) is set
+ * when restricting the [startRevision, endRevision] range to only
+ * those revisions when the paths in targetPaths were changed.
+ *
+ * the 2nd parameter if non-null - is a user's Collection that will
+ * be filled up with found SVNLogEntry objects; it's just another
+ * way to reach the scope.
+ *
+ * startRevision, endRevision - to define a range of revisions you are
+ * interested in; by default in this program - startRevision=0, endRevision=
+ * the latest (HEAD) revision of the repository.
+ *
+ * the 5th parameter - a boolean flag changedPath - if true then for
+ * each revision a corresponding SVNLogEntry will contain a map of
+ * all paths which were changed in that revision.
+ *
+ * the 6th parameter - a boolean flag strictNode - if false and a
+ * changed path is a copy (branch) of an existing one in the repository
+ * then the history for its origin will be traversed; it means the
+ * history of changes of the target URL (and all that there's in that
+ * URL) will include the history of the origin path(s).
+ * Otherwise if strictNode is true then the origin path history won't be
+ * included.
+ *
+ * The return value is a Collection filled up with SVNLogEntry Objects.
+ */
+ logEntries = repository.log(new String[] {""}, null,
+ startRevision, endRevision, true, true);
+
+ } catch (SVNException svne) {
+ handleCollectingLogInformationException(svne,url);
+ }
+ for (Iterator entries = logEntries.iterator(); entries.hasNext();) {
+
+ /*
+ * gets a next SVNLogEntry
+ */
+ SVNLogEntry logEntry = (SVNLogEntry) entries.next();
+ handleLogEntry(logEntry);
+ }
+ finishLogEntries();
+ }
+
+ /*
+ * Initializes the library to work with a repository via
+ * different protocols.
+ */
+ private static void setupLibrary() {
+ /*
+ * For using over http:// and https://
+ */
+ DAVRepositoryFactory.setup();
+ /*
+ * For using over svn:// and svn+xxx://
+ */
+ SVNRepositoryFactoryImpl.setup();
+
+ /*
+ * For using over file:///
+ */
+ FSRepositoryFactory.setup();
+ }
+
+
+}
View
208 src/org/codeswarm/repository/svn/SVNHistory.java
@@ -0,0 +1,208 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+// Some parts of this code have been taken from SVNKit's example-code.
+// See https://wiki.svnkit.com/Printing_Out_Repository_History
+package org.codeswarm.repository.svn;
+
+
+import org.codeswarm.repositoryevents.CodeSwarmEventsSerializer;
+import org.codeswarm.repositoryevents.Event;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import org.codeswarm.repositoryevents.EventList;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNLogEntry;
+import org.tmatesoft.svn.core.SVNLogEntryPath;
+
+/**
+ * Performs the repository lookup and serializes the data.
+ * @author tpraxl
+ */
+public class SVNHistory extends AbstractSVNHistoryVisitor {
+ private static final Logger LOGGER = Logger.getLogger(SVNHistory.class.getName());
+ String filename;
+ String url;
+ EventList list = new EventList();
+ /**
+ * creates an instance of SVNHistory.
+ * @param filename the path to the (xml-)file to serialize the data to.
+ */
+ public SVNHistory(String filename){
+ this.filename =filename;
+ }
+ /**
+ * @return the path to the file the data is serialized to.
+ */
+ public String getFilePath(){
+ return "data/"+filename+this.url.hashCode()+".xml";
+ }
+ /**
+ * clears the entire revision cache.
+ */
+ public static void clearCache(){
+ try {
+ Preferences.userNodeForPackage(SVNHistory.class).clear();
+ } catch (BackingStoreException ex) {
+ Logger.getLogger(SVNHistory.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ /**
+ * stores the repository url
+ * @param url the complete repository url.
+ */
+ public void handleStart(String url) {
+ this.url = url;
+ }
+ /**
+ * looks up the cache. Stops proceeding if a cached version for this
+ * repository was found.
+ * @param pRevision the latest repository revision.
+ * @return false if a cached version was found, true if the history shall
+ * be fetched from repository.
+ */
+ public boolean handleFetchingLatestRepositoryRevision(Long pRevision) {
+ long revision = pRevision.longValue();
+ Preferences p = Preferences.userNodeForPackage(SVNHistory.class);
+ long l= p.getLong(this.url+"_lastRevision", -1l);
+ if(l==revision){
+ LOGGER.log(Level.FINE,"skip fetching {0} (latest revision is {1}) for {2}",new Object[]{String.valueOf(l),revision,this.url});
+ return false;
+ }else{
+ LOGGER.log(Level.FINE, "proceed fetching (latest revision is {0} , cached revision is {1} for repository {2}", new Object[]{String.valueOf(pRevision), String.valueOf(l), this.url});
+ Preferences.userNodeForPackage(SVNHistory.class).putLong(this.url+"_lastRevision", revision);
+ try {
+ Preferences.userNodeForPackage(SVNHistory.class).flush();
+ } catch (BackingStoreException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ }
+ }
+ LOGGER.log(Level.FINE,"fetching until revision {0}",new Object[]{revision});
+ return true;
+ }
+ /**
+ * processes a log entry. Adds it to the EventList
+ * @param logEntry the entry to process
+ */
+ public void handleLogEntry(SVNLogEntry logEntry) {
+ Set keySet = logEntry.getChangedPaths().keySet();
+ Iterator i = keySet.iterator();
+ while(i.hasNext()){
+ String key = (String)i.next();
+ SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(key);
+ list.addEvent(new Event(entryPath.getPath(),logEntry.getDate().getTime(),logEntry.getAuthor()));
+ if(LOGGER.isLoggable(Level.FINE)){
+ LOGGER.log(Level.FINE, "fetched entry {0}\n date {1}\n rev. {2}\n--", new Object[]{entryPath.getPath(),logEntry.getDate(), logEntry.getRevision()});
+ }
+ }
+ /*
+ * displaying all paths that were changed in that revision; changed
+ * path information is represented by SVNLogEntryPath.
+ */
+ if (logEntry.getChangedPaths().size() > 0) {
+ /*
+ * keys are changed paths
+ */
+ Set changedPathsSet = logEntry.getChangedPaths().keySet();
+
+ for (Iterator changedPaths = changedPathsSet.iterator(); changedPaths.hasNext();) {
+ /*
+ * obtains a next SVNLogEntryPath
+ */
+ SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(changedPaths.next());
+ /*
+ * SVNLogEntryPath.getPath returns the changed path itself;
+ *
+ * SVNLogEntryPath.getType returns a charecter describing
+ * how the path was changed ('A' - added, 'D' - deleted or
+ * 'M' - modified);
+ *
+ * If the path was copied from another one (branched) then
+ * SVNLogEntryPath.getCopyPath &
+ * SVNLogEntryPath.getCopyRevision tells where it was copied
+ * from and what revision the origin path was at.
+ */
+ if(LOGGER.isLoggable(Level.FINE)){
+ StringBuffer copyPathInfo = new StringBuffer();
+ if(entryPath.getCopyPath()!=null){
+ copyPathInfo.append("(from ").append(entryPath.getCopyPath());
+ copyPathInfo.append(" rev ").append(entryPath.getCopyRevision()).append(")");
+ }
+ LOGGER.log(Level.FINE,"entry: {0} {1} {2}",
+ new Object[]{entryPath.getType(),entryPath.getPath(),copyPathInfo.toString()});
+ }
+ }
+ }
+ }
+ /**
+ * serializes the log entries
+ */
+ public void finishLogEntries() {
+ try {
+ CodeSwarmEventsSerializer serializer =
+ new CodeSwarmEventsSerializer(list);
+ serializer.serialize(getFilePath());
+ } catch (ParserConfigurationException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ } catch (TransformerConfigurationException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ } catch (TransformerException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ }
+ }
+ /**
+ * Logs an error statement and stops further processing
+ * @param e the orginal exception
+ * @param url the repository url
+ * @return false
+ */
+ public boolean handleCreateRepositoryException(SVNException e, String url) {
+ /*
+ * Perhaps a malformed URL is the cause of this exception.
+ */
+ LOGGER.log(Level.SEVERE,"error while creating an SVNRepository for the location {0} : {1}", new Object[]{url, e.getMessage()});
+ return false;
+ }
+ /**
+ * Logs an error statement and stops further processing
+ * Otherwise returns the latest cached revision.
+ * @param e the orginal exception
+ * @return null.
+ */
+ public Long handleFetchingLatestRepositoryRevisionException(SVNException svne) {
+ LOGGER.log(Level.FINE,"error while fetching the latest repository revision: {0}.\nFalling back to cached version (if present).",new Object[]{ svne.getMessage()});
+ return null;
+ }
+ /**
+ * Logs an error statement and stops further processing
+ * @param e the orginal exception
+ * @param url the repository url
+ * @return false
+ */
+ public boolean handleCollectingLogInformationException(SVNException svne, String url) {
+ LOGGER.log(Level.SEVERE,"error while collecting log information for {0} : {1}", new Object[]{url,svne.getMessage()});
+ return false;
+ }
+}
View
80 src/org/codeswarm/repositoryevents/CodeSwarmEventsSerializer.java
@@ -0,0 +1,80 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.codeswarm.repositoryevents;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Takes a list and renders it to an xml-file
+ * @author tpraxl
+ */
+public class CodeSwarmEventsSerializer {
+ EventList list;
+ /**
+ * creates an instance of the serializer.
+ * @param list the EventList to serialize
+ */
+ public CodeSwarmEventsSerializer(EventList list){
+ this.list = list;
+ }
+ /**
+ * actually serializes the list to the file denoted by pathToFile
+ * @param pathToFile the path to the xml file to serialize to.
+ * It gets created if it doesn't exist.
+ * @throws javax.xml.parsers.ParserConfigurationException
+ * When the serialization failed
+ * @throws javax.xml.transform.TransformerConfigurationException
+ * When the serialization failed
+ * @throws java.io.IOException
+ * When the serialization failed
+ * @throws javax.xml.transform.TransformerException
+ * When the serialization failed
+ */
+ public void serialize(String pathToFile) throws ParserConfigurationException, TransformerConfigurationException, IOException, TransformerException{
+ Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ Element events = d.createElement("file_events");
+ for(Event e : list.getEvents()){
+ Element event = d.createElement("event");
+ event.setAttribute("filename", e.getFilename());
+ event.setAttribute("date", String.valueOf(e.getDate()));
+ event.setAttribute("author", e.getAuthor());
+ events.appendChild(event);
+ }
+ d.appendChild(events);
+ Transformer t = TransformerFactory.newInstance().newTransformer();
+ File f = new File(pathToFile);
+ if(!f.exists()){
+ f.createNewFile();
+ }
+ FileOutputStream out = new FileOutputStream(f);
+ StreamResult result = new StreamResult(out);
+ t.transform(new DOMSource(d), result);
+ out.close();
+ }
+
+}
View
79 src/org/codeswarm/repositoryevents/Event.java
@@ -0,0 +1,79 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.codeswarm.repositoryevents;
+
+/**
+ * represents a repository event or a log entry. Just a simple bean.
+ * @author tpraxl
+ */
+public class Event {
+ private String filename;
+ private long date;
+ private String author;
+ /**
+ * creates an instance.
+ * @param filename the path of the file that was changed
+ * @param date the date of modification
+ * @param author the user that changed the file
+ */
+ public Event(String filename, long date, String author){
+ this.filename = filename;
+ this.date = date;
+ this.author = author;
+ }
+
+ /**
+ * @return the filename
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * @param filename the filename to set
+ */
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ /**
+ * @return the date
+ */
+ public long getDate() {
+ return date;
+ }
+
+ /**
+ * @param date the date to set
+ */
+ public void setDate(long date) {
+ this.date = date;
+ }
+
+ /**
+ * @return the author
+ */
+ public String getAuthor() {
+ return author;
+ }
+
+ /**
+ * @param author the author to set
+ */
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+}
View
49 src/org/codeswarm/repositoryevents/EventList.java
@@ -0,0 +1,49 @@
+/* This file is part of code_swarm.
+
+code_swarm 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.
+
+code_swarm 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 code_swarm. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.codeswarm.repositoryevents;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * represents a list of log entries. Just a simple bean.
+ * @author tpraxl
+ */
+public class EventList {
+ List<Event> events = new ArrayList<Event>();
+ /**
+ * add an entry to the list.
+ * @param e the repository Event / log entry to add (not null)
+ */
+ public void addEvent(Event e){
+ events.add(e);
+ }
+ /**
+ * @return an iterator-view of the list
+ */
+ public Iterator<Event> iterator(){
+ return events.iterator();
+ }
+ /**
+ * @return an unmodifiableList-View of the list
+ */
+ public List<Event> getEvents(){
+ return Collections.unmodifiableList(events);
+ }
+}

0 comments on commit 5d0452a

Please sign in to comment.
Something went wrong with that request. Please try again.