package votorola.a.register; // Copyright 2008, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import java.io.*; import java.util.*; import java.util.concurrent.locks.*; import java.sql.*; import javax.script.*; import votorola._.*; import votorola.a.*; import votorola.a.locale.*; import votorola.a.register.trust.*; import votorola.a.voter.*; import votorola.g.lang.*; import votorola.g.logging.*; import votorola.g.script.*; import votorola.g.sql.*; import votorola.g.util.*; /** A register, provided as a formal electoral service. * * @see ../design.xht#electoral-register */ @ThreadRestricted("holds lock()") public final class Register extends ElectoralService implements InputStore { // Cf. a/election/Election /** Constructs a Register. * * @param s the compiled configuration script */ public static @ThreadSafe Register newRegister( ElectoralSubserver.Run subserverRun, JavaScriptIncluder s ) throws ScriptException, SQLException { final ConfigurationContext cc = new ConfigurationContext( subserverRun.subserver(), s ); s.invokeKnownFunction( "configureRegister", cc ); return new Register( subserverRun, cc ); } private Register( ElectoralSubserver.Run subserverRun, final ConfigurationContext cc ) throws ScriptException, SQLException { super( subserverRun, cc.name, cc ); ArrayList responderList = new ArrayList(); responderList.add( new CR_Doubt( Register.this )); responderList.add( new CR_Undoubt( Register.this )); responderList.add( new CR_Set( Register.this )); responderList.add( new CR_Unset( Register.this )); responderList.add( new CR_Trust( Register.this )); responderList.add( new CR_Untrust( Register.this )); init( responderList ); configurationContext = null; // done with it, free the memory } private ConfigurationContext cc() { return (ConfigurationContext)configurationContext; } // nulled after init // ```````````````````````````````````````````````````````````````````````````````````` // Initialized early, for use in other initializers. private final File configurationFile = cc().configurationFile; { Database d = new Database( cc().listDatabase() ); // try // { listDatabase = subserverRun.getOrInitializeDatabase( d ); // } // finally{ listDatabase.logAndClearWarnings(); } // to show SQLState //// but no warnings recorded there } // ------------------------------------------------------------------------------------ /** The directory containing this register's configuration files. */ @ThreadSafe File configurationDirectory() { return configurationFile.getParentFile(); } /** The relational store of doubt signals, backing this register's doubt signaling * network. It is a schema.table named "{@linkplain #name() register-name}.doubt", * stored in the list database. * * @see #listDatabase() */ public @ThreadSafe DoubtSignal.Table doubtTable() { return doubtTable; } // not present in super-registers, when those are implemented private final DoubtSignal.Table doubtTable; { ensureSchema( listDatabase(), name() ); doubtTable = new DoubtSignal.Table( name(), listDatabase() ); } /** The estimated size of the electorate, if known. This a best guess of the number * of people who would be eligible for the voter list, if they chose to register. It * might be the population size, or it might be something less; it depends on the * {@linkplain #listScript() configuration of the voter list}. * * @return estimated size of the electorate; or zero, if unknown * @see ConfigurationContext#setElectorateSize(long) */ @ThreadSafe long electorateSize() { return electorateSize; } private final long electorateSize = cc().getElectorateSize(); /** An explanation of the estimate of the size of the electorate, for the information * of users. For example: *
* "population of region (2008 census)" *
* * @see ConfigurationContext#setElectorateSizeExplanation(String) */ @ThreadSafe String electorateSizeExplanation() { return electorateSizeExplanation; } private final String electorateSizeExplanation = cc().getElectorateSizeExplanation(); /** A brief description of the allowable content of this register's * {@linkplain Registration#getLink() link field}, in sentence form. * * @see ConfigurationContext#setFieldDescription_link(String) */ public @ThreadSafe String fieldDescription_link() { return fieldDescription_link; } private final String fieldDescription_link = cc().getFieldDescription_link(); /** An example of the allowable content for this register's * {@linkplain Registration#getLink() link field}. * * @see ConfigurationContext#setFieldExample_link(String) */ public @ThreadSafe String fieldExample_link() { return fieldExample_link; } private final String fieldExample_link = cc().getFieldExample_link(); /** A brief description of the allowable content of this register's * {@linkplain Registration#getName() name field}, in sentence form. * * @see ConfigurationContext#setFieldDescription_name(String) */ public @ThreadSafe String fieldDescription_name() { return fieldDescription_name; } private final String fieldDescription_name = cc().getFieldDescription_name(); /** An example of the allowable content for this register's * {@linkplain Registration#getName() name field}. * * @see ConfigurationContext#setFieldExample_name(String) */ public @ThreadSafe String fieldExample_name() { return fieldExample_name; } private final String fieldExample_name = cc().getFieldExample_name(); /** A brief description of the allowable content of this register's * {@linkplain Registration#getNote() note field}, in sentence form. * * @see ConfigurationContext#setFieldDescription_note(String) */ public @ThreadSafe String fieldDescription_note() { return fieldDescription_note; } private final String fieldDescription_note = cc().getFieldDescription_note(); /** An example of the allowable content for this register's * {@linkplain Registration#getNote() note field}. * * @see ConfigurationContext#setFieldExample_note(String) */ public @ThreadSafe String fieldExample_note() { return fieldExample_note; } private final String fieldExample_note = cc().getFieldExample_note(); /** A brief description of the allowable content of this register's * {@linkplain Registration#getResidence() residence field}, in sentence form. * * @see ConfigurationContext#setFieldDescription_residence(String) */ public @ThreadSafe String fieldDescription_residence() { return fieldDescription_residence; } private final String fieldDescription_residence = cc().getFieldDescription_residence(); /** An example of the allowable content for this register's * {@linkplain Registration#getResidence() residence field}. * * @see ConfigurationContext#setFieldExample_residence(String) */ public @ThreadSafe String fieldExample_residence() { return fieldExample_residence; } private final String fieldExample_residence = cc().getFieldExample_residence(); /** The geocoding method, for conversion of addresses to cartographic coordinates * in the {@linkplain #trustScript() trust script}. * * @return geocoding method, or null if none is used * * @see ConfigurationContext#setGeocodingMethodGoogle(String) */ public @ThreadSafe Geocode.GoogleGeocoding geocodingMethod() { return geocodingMethod; } private final Geocode.GoogleGeocoding geocodingMethod = cc().getGeocodingMethod(); /** Retrieves a voter's list node from a compiled voter list. This is a convenience * method. * * @param list the voter list to use; or null, to lookup the currently * {@linkplain #listToReport() reported list} (which involves locking overhead * in threaded runs) * * @return ListNodeC or ListNodeIC * per {@linkplain ListNodeC.Table#getOrCreate(String) getOrCreate}(voterEmail); * or, if lookup of the list itself fails, a ListNode0 with default values */ public @ThreadSafe ListNode getListNode( VoterList list, final String voterEmail ) throws IOException, SQLException { if( list == null ) { lock().lock(); try { list = listToReport(); } finally { lock().unlock(); } } final ListNode listNode; if( list == null ) listNode = new ListNode0( voterEmail ); else listNode = list.listNodeTable().getOrCreate( voterEmail ); return listNode; } /** Database for mounting the relational parts of this register's compiled voter * lists, and its doubt table -- a reference to a shared instance (not thread safe). *

* Although this method is thread safe, the object it returns is not; * it has its own thread-saftety restrictions, q.v. *

* * @see #doubtTable() * @see #listToReport() * @see ConfigurationContext#listDatabase() */ public @ThreadSafe Database listDatabase() { return listDatabase; } private final Database listDatabase; /** The script 'list.js', for configuring the compilation of voter lists. It is * located in the {@linkplain #configurationDirectory() configuration directory}. * The language is JavaScript. There are restrictions on the {@linkplain * votorola.g.script.JavaScriptIncluder character encoding}. * * @see list.js (example script) */ public JavaScriptIncluder listScript() { assert lock.isHeldByCurrentThread(); // this method is safe, but not the object return listScript; } private final JavaScriptIncluder listScript = new JavaScriptIncluder( new File( configurationDirectory(), "list.js" )); /** The summary description for newly compiled voter lists. * * @see VoterList#summaryDescription() * @see ConfigurationContext#setListSummaryDescription(String) */ public @ThreadSafe String listSummaryDescription() { return listSummaryDescription; } private final String listSummaryDescription = cc().getListSummaryDescription(); /** Returns the current voter list to report, if any. * * @return voter list, or null if none to report * * @see #readyToReportLink() */ public VoterList listToReport() throws IOException { assert lock.isHeldByCurrentThread(); ReadyDirectory readyDirectory = null; // so far if( readyToReportLink.exists() ) { readyDirectory = new ReadyDirectory( readyToReportLink.getCanonicalPath() ); } if( readyDirectory == null ) { if( listToReport != null ) { LoggerX.i(getClass()).info( readyToReportLink + ": link is lost, stopping report: " + listToReport.readyDirectory() ); listToReport = null; } } else if( !readyDirectory.isMounted() ) { LoggerX.i(getClass()).warning( readyToReportLink + ": list not mounted: " + readyDirectory ); listToReport = null; } else if( listToReport == null || !listToReport.readyDirectory().equals( readyDirectory )) { LoggerX.i(getClass()).info( readyToReportLink + ": starting new report: " + readyDirectory ); listToReport = VoterList.readObjectFromMountedListFile( readyDirectory ); listToReport.init( new ListNodeC.Table( readyDirectory, listDatabase ), new Neighbourhood.Table( readyDirectory, listDatabase ), new TrustEdge.Table( readyDirectory, listDatabase )); } return listToReport; } private VoterList listToReport; // lazilly set/reset through listToReport() /** The minimum level of trust that a registrant must have, in order to pre-register * another registrant. * * @see Registration#isWriteableAll() * @see ConfigurationContext#setPreRegistrationTrustLevel(int) */ public @ThreadSafe int preRegistrationTrustLevel() { return preRegistrationTrustLevel; } private final int preRegistrationTrustLevel = cc().getPreRegistrationTrustLevel(); /** The configured list of primary trust edges, for the neighbourhood trust network. * * @return unmodifiable list of edges * * @see ConfigurationContext#addPrimaryTrust(String,int) */ java.util.List primaryTrustList() { return primaryTrustList; } private final ArrayListU primaryTrustList = new ArrayListU( cc().getPrimaryTrustArray() ); /** Returns the symbolic link to the ready directory * of the current voter list to report, if any. * * @return abstract path (never null) of symbolic link * * @see #listToReport() */ public @ThreadSafe File readyToReportLink() { return readyToReportLink; } /** The script 'trust.js', for restricting the scope of trust edges among voters. It * is located in the {@linkplain #configurationDirectory() configuration directory}. * The language is JavaScript. There are restrictions on the {@linkplain * votorola.g.script.JavaScriptIncluder character encoding}. * * @see trust/trust.js (example script) * @see ../manual.xht#trust-script */ public JavaScriptIncluder trustScript() { assert lock.isHeldByCurrentThread(); // this method is safe, but not the object return trustScript; } private final JavaScriptIncluder trustScript = new JavaScriptIncluder( new File( configurationFile.getParentFile(), "trust.js" )); // - E l e c t o r a l - S e r v i c e ------------------------------------------------ public @ThreadSafe @Override File configurationFile() { return configurationFile; } public @Override Exception dispatch( final String[] argArray, final CommandResponder.Session commandSession ) { voterInputTable.database().logAndClearWarnings(); try{ return super.dispatch( argArray, commandSession ); } finally{ voterInputTable.database().logAndClearWarnings(); } } /** @see ConfigurationContext#setSummaryDescription(String) */ public @Override @ThreadSafe String summaryDescription() { return summaryDescription; } private final String summaryDescription = cc().getSummaryDescription(); /** Title of this electoral register, in title case. * * @see ConfigurationContext#setTitle(String) */ public @Override @ThreadSafe String title() { return title; } private final String title = cc().getTitle(); // - I n p u t - S t o r e ------------------------------------------------------------ public @ThreadSafe @Override String snapshotOutputStoreFilebase() { return snapshotOutputStoreFilebase; } private final String snapshotOutputStoreFilebase = subserverRun.subserver().cacheDirectory().getPath() + File.separator + "snapout"; private final File readyToReportLink = new File( snapshotOutputStoreFilebase + File.separator + name() + File.separator + "_readyList_report" ); /** @see ConfigurationContext#voterInputDatabase() */ public @ThreadSafe @Override VoterInputTable voterInputTable() { return voterInputTable; } private final VoterInputTable voterInputTable; { Database d = new Database( cc().voterInputDatabase() ); // try // { d = subserverRun.getOrInitializeDatabase( d ); // } // finally{ database.logAndClearWarnings(); } // to show SQLState //// but no warnings recorded there voterInputTable = new VoterInputTable( Register.this, d ); } // ==================================================================================== /** A context for configuring a {@linkplain Register register}. * Each register is configured by its * {@linkplain Register#configurationFile configuration file}, * which contains a script (s) for that purpose. * During construction of the register, an instance of this context (registerCC) * is passed to s, via s::configureRegister(registerCC). */ public static @ThreadSafe final class ConfigurationContext // public class and getters, accessible by configuration scripts { private ConfigurationContext( ElectoralSubserver subserver, JavaScriptIncluder s ) { configurationFile = s.scriptFile(); name = configurationFile.getParentFile().getName(); listDatabase = new ElectoralSubserver.DatabaseCC( subserver.name() ); setListSummaryDescription( "This is the current voter list, " + "organized by neighbourhoods. " + "Further information is unavailable, because the 'configureRegister' function " + "of script " + configurationFile + " " + "makes no call to 'setListSummaryDescription'." ); setSummaryDescription( "This is the electoral register. " + "Further information is unavailable, because the 'configureRegister' function " + "of script " + configurationFile + " " + "makes no call to 'setSummaryDescription'." ); voterInputDatabase = new ElectoralSubserver.DatabaseCC( subserver.name() ); final BundleFormatter bun = new BundleFormatter( ResourceBundle.getBundle( "votorola.a.locale.A", Locale.getDefault() )); setFieldDescription_link( bun.l( "a.register.Register.fieldDescription_link" )); setFieldExample_link( bun.l( "a.register.Register.fieldExample_link" )); setFieldDescription_name( bun.l( "a.register.Register.fieldDescription_name" )); setFieldExample_name( bun.l( "a.register.Register.fieldExample_name" )); setFieldDescription_note( bun.l( "a.register.Register.fieldDescription_note" )); setFieldExample_note( bun.l( "a.register.Register.fieldExample_note" )); setFieldDescription_residence( bun.l( "a.register.Register.fieldDescription_residence" )); setFieldExample_residence( bun.l( "a.register.Register.fieldExample_residence" )); } private final File configurationFile; private final String name; // -------------------------------------------------------------------------------- /** @see Register#electorateSize() * @see #setElectorateSize(long) */ public long getElectorateSize() { return electorateSize; } private long electorateSize; /** Sets the estimated size of the electorate. The default value is zero, * meaning unknown. * * @see Register#electorateSize() */ @ThreadRestricted("constructor") public void setElectorateSize( long electorateSize ) { this.electorateSize = electorateSize; } /** @see Register#electorateSizeExplanation() * @see #setElectorateSizeExplanation(String) */ public String getElectorateSizeExplanation() { return electorateSizeExplanation; } private String electorateSizeExplanation = "number of potential registrants"; /** Sets the electorate size explanation. The default value is * "number of potential registrants". * * @see Register#electorateSizeExplanation() */ @ThreadRestricted("constructor") public void setElectorateSizeExplanation( String electorateSizeExplanation ) { this.electorateSizeExplanation = electorateSizeExplanation; } /** @see Register#fieldDescription_link() * @see #setFieldDescription_link(String) */ public String getFieldDescription_link() { return fieldDescription_link; } private String fieldDescription_link; /** Sets the description of the link field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldDescription_link() */ @ThreadRestricted("constructor") public void setFieldDescription_link( String fieldDescription_link ) { if( fieldDescription_link.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldDescription_link = fieldDescription_link; } /** @see Register#fieldExample_link() * @see #setFieldExample_link(String) */ public String getFieldExample_link() { return fieldExample_link; } private String fieldExample_link; /** Sets the description of the link field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldExample_link() */ @ThreadRestricted("constructor") public void setFieldExample_link( String fieldExample_link ) { if( fieldExample_link.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldExample_link = fieldExample_link; } /** @see Register#fieldDescription_name() * @see #setFieldDescription_name(String) */ public String getFieldDescription_name() { return fieldDescription_name; } private String fieldDescription_name; /** Sets the description of the name field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldDescription_name() */ @ThreadRestricted("constructor") public void setFieldDescription_name( String fieldDescription_name ) { if( fieldDescription_name.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldDescription_name = fieldDescription_name; } /** @see Register#fieldExample_name() * @see #setFieldExample_name(String) */ public String getFieldExample_name() { return fieldExample_name; } private String fieldExample_name; /** Sets the description of the name field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldExample_name() */ @ThreadRestricted("constructor") public void setFieldExample_name( String fieldExample_name ) { if( fieldExample_name.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldExample_name = fieldExample_name; } /** @see Register#fieldDescription_note() * @see #setFieldDescription_note(String) */ public String getFieldDescription_note() { return fieldDescription_note; } private String fieldDescription_note; /** Sets the description of the note field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldDescription_note() */ @ThreadRestricted("constructor") public void setFieldDescription_note( String fieldDescription_note ) { if( fieldDescription_note.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldDescription_note = fieldDescription_note; } /** @see Register#fieldExample_note() * @see #setFieldExample_note(String) */ public String getFieldExample_note() { return fieldExample_note; } private String fieldExample_note; /** Sets the description of the note field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldExample_note() */ @ThreadRestricted("constructor") public void setFieldExample_note( String fieldExample_note ) { if( fieldExample_note.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldExample_note = fieldExample_note; } /** @see Register#fieldDescription_residence() * @see #setFieldDescription_residence(String) */ public String getFieldDescription_residence() { return fieldDescription_residence; } private String fieldDescription_residence; /** Sets the description of the residence field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldDescription_residence() */ @ThreadRestricted("constructor") public void setFieldDescription_residence( String fieldDescription_residence ) { if( fieldDescription_residence.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldDescription_residence = fieldDescription_residence; } /** @see Register#fieldExample_residence() * @see #setFieldExample_residence(String) */ public String getFieldExample_residence() { return fieldExample_residence; } private String fieldExample_residence; /** Sets the description of the residence field. * The default value depends on the server's locale. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#fieldExample_residence() */ @ThreadRestricted("constructor") public void setFieldExample_residence( String fieldExample_residence ) { if( fieldExample_residence.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.fieldExample_residence = fieldExample_residence; } /** @see Register#geocodingMethod() * @see #setGeocodingMethodGoogle(String) */ public Geocode.GoogleGeocoding getGeocodingMethod() { return geocodingMethod; } private Geocode.GoogleGeocoding geocodingMethod; /** Sets the geocoding method of the register * to {@linkplain votorola.a.Geocode.GoogleGeocoding GoogleGeocoding}. * * @param key per {@linkplain votorola.a.Geocode.GoogleGeocoding#key key}() * * @see Register#geocodingMethod() */ @ThreadRestricted("constructor") public void setGeocodingMethodGoogle( String key ) { geocodingMethod = new Geocode.GoogleGeocoding( key ); } /** @see VoterList#summaryDescription() * @see #setListSummaryDescription(String) */ public @Deprecated String getListSummaryDescription() { return listSummaryDescription; } private String listSummaryDescription; /** Sets the summary description for newly compiled voter lists. The default * value is a placeholder with configuration instructions for the * administrator. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see VoterList#summaryDescription() */ @Deprecated @ThreadRestricted("constructor") public void setListSummaryDescription( String listSummaryDescription ) { if( listSummaryDescription.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.listSummaryDescription = listSummaryDescription; } /** @see Register#preRegistrationTrustLevel() * @see #setPreRegistrationTrustLevel(int) */ public int getPreRegistrationTrustLevel() { return preRegistrationTrustLevel; } private int preRegistrationTrustLevel; /** Sets the pre-registration trust level. The default value is zero. * * @see Register#preRegistrationTrustLevel() */ @ThreadRestricted("constructor") public void setPreRegistrationTrustLevel( int preRegistrationTrustLevel ) { this.preRegistrationTrustLevel = preRegistrationTrustLevel; } /** @see Register#primaryTrustList() * @see #addPrimaryTrust(String,int) */ public TrustEdge.Primary[] getPrimaryTrustArray() { return primaryTrustList.toArray( new TrustEdge.Primary[primaryTrustList.size()] ); } private final ArrayList primaryTrustList = new ArrayList(); /** Adds primary trust edges to the neighbourhood network. * By default, there are no primary trust edges (and therefore * no trust in the network). * * @param voter1Email per * TrustEdge.Primary.{@linkplain votorola.a.register.trust.TrustEdge.Primary#voter1Email voter1Email}() * @param edgeCount per * TrustEdge.Primary.{@linkplain votorola.a.register.trust.TrustEdge.Primary#edgeCount edgeCount}() * * @see Register#primaryTrustList() */ @ThreadRestricted("constructor") public void addPrimaryTrust( String voter1Email, int edgeCount ) { primaryTrustList.add( new TrustEdge.Primary( voter1Email, edgeCount )); } /** @see Register#summaryDescription() * @see #setSummaryDescription(String) */ public String getSummaryDescription() { return summaryDescription; } private String summaryDescription; /** Sets the summary description of the register. The default value is a * placeholder with configuration instructions for the administrator. * * @throws IllegalArgumentException if the description contains * any newline characters, because they might render inconsistently * across different types of user interface * @see Register#summaryDescription() */ @ThreadRestricted("constructor") public void setSummaryDescription( String summaryDescription ) { if( summaryDescription.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); this.summaryDescription = summaryDescription; } /** @see Register#title() * @see #setTitle(String) */ public String getTitle() { return title; } private String title = "Electoral Register"; /** Sets the title of the register. The default value is "Electoral Register". * * @see Register#title() */ @ThreadRestricted("constructor") public void setTitle( String title ) { this.title = title; } /** The context for configuring the register's * {@linkplain Register#listDatabase() list database}. */ public ElectoralSubserver.DatabaseCC listDatabase() { return listDatabase; } private final ElectoralSubserver.DatabaseCC listDatabase; /** The context for configuring the database of the register's * {@linkplain Register#voterInputTable() voter input table}. */ public ElectoralSubserver.DatabaseCC voterInputDatabase() { return voterInputDatabase; } private final ElectoralSubserver.DatabaseCC voterInputDatabase; } }