> duplicates = ff.getDuplicates();
23 | Assert.assertFalse(duplicates.isEmpty());
24 | duplicates.forEach((k, v) -> System.out.println(k + ": " + v.size()));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Chapter03/lib/src/test/resources/log4j2.yaml:
--------------------------------------------------------------------------------
1 | Configuration:
2 | status: info
3 |
4 | Appenders:
5 | Console:
6 | name: CONSOLE
7 | target: SYSTEM_OUT
8 | PatternLayout:
9 | Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n"
10 | # RollingFile:
11 | # - name: APPLICATION
12 | # fileName: ../logs/my-app.log
13 | # filePattern: "../logs/$${date:yyyy-MM}/my-app-%d{yyyy-MM-dd}-%i.log.gz"
14 | # PatternLayout:
15 | # Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n"
16 | # policies:
17 | # TimeBasedTriggeringPolicy:
18 | # interval: 1
19 | # modulate: true
20 | Loggers:
21 | Root:
22 | level: debug
23 | AppenderRef:
24 | - ref: CONSOLE
25 | Logger:
26 | - name: com.steeplesoft.dupefinder.lib
27 | additivity: false
28 | level: debug
29 | AppenderRef:
30 | - ref: CONSOLE
--------------------------------------------------------------------------------
/Chapter03/test-data/set1/file1.txt:
--------------------------------------------------------------------------------
1 | Hoc est non modo cor non habere, sed ne palatum quidem.
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Qua tu etiam inprudens utebare non numquam. Duo Reges: constructio interrete. Quae duo sunt, unum facit. Nec enim, omnes avaritias si aeque avaritias esse dixerimus, sequetur ut etiam aequas esse dicamus. Et adhuc quidem ita nobis progresso ratio est, ut ea duceretur omnis a prima commendatione naturae. Haeret in salebra. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.
4 |
5 |
6 | Utrum enim sit voluptas in iis rebus, quas primas secundum naturam esse diximus, necne sit ad id, quod agimus, nihil interest.
7 |
8 |
9 |
10 |
11 | Res enim fortasse verae, certe graves, non ita tractantur,
12 | ut debent, sed aliquanto minutius.
13 |
14 | Sed haec in pueris;
15 |
16 |
17 |
18 |
19 | Memini vero, inquam;
20 | Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest.
21 | Tenent mordicus.
22 | Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.
23 | Peccata paria.
24 | Dulce amarum, leve asperum, prope longe, stare movere, quadratum rotundum.
25 | Nihil illinc huc pervenit.
26 | Itaque eos id agere, ut a se dolores, morbos, debilitates repellant.
27 | At certe gravius.
28 | Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi.
29 |
30 |
31 |
32 |
33 | Itaque dicunt nec dubitant: mihi sic usus est, tibi ut opus est facto, fac.
34 | Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere.
35 | Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere.
36 | Non autem hoc: igitur ne illud quidem.
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Chapter03/test-data/set2/file1.txt:
--------------------------------------------------------------------------------
1 | Hoc est non modo cor non habere, sed ne palatum quidem.
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Qua tu etiam inprudens utebare non numquam. Duo Reges: constructio interrete. Quae duo sunt, unum facit. Nec enim, omnes avaritias si aeque avaritias esse dixerimus, sequetur ut etiam aequas esse dicamus. Et adhuc quidem ita nobis progresso ratio est, ut ea duceretur omnis a prima commendatione naturae. Haeret in salebra. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.
4 |
5 |
6 | Utrum enim sit voluptas in iis rebus, quas primas secundum naturam esse diximus, necne sit ad id, quod agimus, nihil interest.
7 |
8 |
9 |
10 |
11 | Res enim fortasse verae, certe graves, non ita tractantur,
12 | ut debent, sed aliquanto minutius.
13 |
14 | Sed haec in pueris;
15 |
16 |
17 |
18 |
19 | Memini vero, inquam;
20 | Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest.
21 | Tenent mordicus.
22 | Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.
23 | Peccata paria.
24 | Dulce amarum, leve asperum, prope longe, stare movere, quadratum rotundum.
25 | Nihil illinc huc pervenit.
26 | Itaque eos id agere, ut a se dolores, morbos, debilitates repellant.
27 | At certe gravius.
28 | Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi.
29 |
30 |
31 |
32 |
33 | Itaque dicunt nec dubitant: mihi sic usus est, tibi ut opus est facto, fac.
34 | Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere.
35 | Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere.
36 | Non autem hoc: igitur ne illud quidem.
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | datecalc-master
7 | 1.0-SNAPSHOT
8 |
9 | datecalc-cli
10 | Date Calculator - CLI
11 | jar
12 |
13 |
14 | ${project.groupId}
15 | datecalc-lib
16 | ${project.version}
17 |
18 |
19 | org.tomitribe
20 | tomitribe-crest
21 |
22 |
23 |
24 |
25 | install
26 |
27 |
28 | maven-shade-plugin
29 | ${plugin.shade}
30 |
31 |
32 | package
33 |
34 | shade
35 |
36 |
37 |
38 |
39 | org.tomitribe.crest.Main
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-cli/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module datecalc.cli {
2 | requires datecalc.lib;
3 | requires tomitribe.crest;
4 | requires tomitribe.crest.api;
5 |
6 | exports com.steeplesoft.datecalc.cli;
7 | }
8 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-cli/src/main/resources/crest-commands.txt:
--------------------------------------------------------------------------------
1 | com.steeplesoft.j9blueprints.datecalc.cli.DateCalc
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | datecalc-master
7 | 1.0-SNAPSHOT
8 |
9 | datecalc-lib
10 | Date Calculator - Library
11 | jar
12 |
13 |
14 | 2.5.0
15 | 2.1.1
16 |
17 |
18 |
19 |
20 | org.eclipse.persistence
21 | javax.persistence
22 | ${jpa.version}
23 |
24 |
25 |
26 | org.eclipse.persistence
27 | eclipselink
28 | ${eclipse.version}
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/DateCalcException.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.datecalc;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public class DateCalcException extends RuntimeException {
8 | public DateCalcException(String message) {
9 | super(message);
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/DateCalculatorResult.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.datecalc;
2 |
3 | import java.time.Duration;
4 | import java.time.LocalDate;
5 | import java.time.LocalTime;
6 | import java.time.Period;
7 | import java.util.Optional;
8 |
9 | /**
10 | *
11 | * @author jason
12 | */
13 | public class DateCalculatorResult {
14 | private Period period;
15 | private Duration duration;
16 | private LocalDate date;
17 | private LocalTime time;
18 | private String expression;
19 |
20 | public DateCalculatorResult(Period period) {
21 | this.period = period;
22 | }
23 |
24 | public DateCalculatorResult(Duration duration) {
25 | this.duration = duration;
26 | }
27 |
28 | public DateCalculatorResult(LocalDate date) {
29 | this.date = date;
30 | }
31 |
32 | public DateCalculatorResult(LocalTime time) {
33 | this.time = time;
34 | }
35 |
36 | public Optional getPeriod() {
37 | return Optional.ofNullable(period);
38 | }
39 |
40 | public Optional getDuration() {
41 | return Optional.ofNullable(duration);
42 | }
43 |
44 | public Optional getDate() {
45 | return Optional.ofNullable(date);
46 | }
47 |
48 | public Optional getTime() {
49 | return Optional.ofNullable(time);
50 | }
51 |
52 | public String getExpression() {
53 | return expression;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/DateToken.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.datecalc.parser.token;
7 |
8 | import com.steeplesoft.datecalc.DateCalcException;
9 | import java.time.LocalDate;
10 | import java.time.format.DateTimeParseException;
11 | import java.util.Optional;
12 |
13 | /**
14 | *
15 | * @author jason
16 | */
17 | public class DateToken extends Token {
18 | private static final String TODAY = "today";
19 | public static String REGEX = "\\d{4}[-/][01]\\d[-/][0123]\\d|today"; // YYYY-MM-DD or YYYY/MM/DD
20 |
21 | public static class Info implements Token.Info {
22 | @Override
23 | public String getRegex() {
24 | return REGEX;
25 | }
26 |
27 | @Override
28 | public DateToken getToken(String text) {
29 | return of(text);
30 | }
31 | }
32 |
33 | private DateToken(LocalDate value) {
34 | this.value = value;
35 | }
36 |
37 | public static DateToken of(String text) {
38 | try {
39 | return TODAY.equals(text.toLowerCase())
40 | ? new DateToken(LocalDate.now())
41 | : new DateToken(LocalDate.parse(text.replace("/", "-")));
42 | } catch (DateTimeParseException ex) {
43 | throw new DateCalcException("Invalid date format: " + text);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/IntegerToken.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.datecalc.parser.token;
2 |
3 | import com.steeplesoft.datecalc.DateCalcException;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public class IntegerToken extends Token {
10 |
11 | public static final String REGEX = "\\d+";
12 |
13 | public static class Info implements Token.Info {
14 |
15 | @Override
16 | public String getRegex() {
17 | return REGEX;
18 | }
19 |
20 | @Override
21 | public IntegerToken getToken(String text) {
22 | return of(text);
23 | }
24 | }
25 |
26 | private IntegerToken(Integer value) {
27 | this.value = value;
28 | }
29 |
30 | public static IntegerToken of(String text) {
31 | try {
32 | return new IntegerToken(Integer.valueOf(text));
33 | } catch (NumberFormatException ex) {
34 | throw new DateCalcException("Invalid number: " + text);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/OperatorToken.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.datecalc.parser.token;
2 |
3 | import com.steeplesoft.datecalc.DateCalcException;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public class OperatorToken extends Token {
10 |
11 | public static final String MINUS = "-";
12 | public static final String PLUS = "+";
13 | public static final String REGEX = "\\+|-|to";
14 |
15 | public static class Info implements Token.Info {
16 | @Override
17 | public String getRegex() {
18 | return REGEX;
19 | }
20 |
21 | @Override
22 | public OperatorToken getToken(String text) {
23 | return of(text);
24 | }
25 | }
26 |
27 | private OperatorToken(String value) {
28 | this.value = value;
29 | }
30 |
31 | public boolean isAddition() {
32 | return PLUS.equals(value);
33 | }
34 |
35 | public static OperatorToken of(String text) {
36 | if (PLUS.equals(text) || MINUS.equals(text)) {
37 | return new OperatorToken(text);
38 | } else {
39 | throw new DateCalcException("Invalid operator specified: " + text);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/TimeZoneToken.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.datecalc.parser.token;
7 |
8 | import com.steeplesoft.datecalc.DateCalcException;
9 | import java.time.ZoneId;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | public class TimeZoneToken extends Token {
16 | public static final String REGEX = "(?:UTC|GMT)?(?:[\\+-][01]*[0-9]+)";
17 | public static class Info implements Token.Info {
18 | @Override
19 | public String getRegex() {
20 | return REGEX;
21 | }
22 |
23 | @Override
24 | public TimeZoneToken getToken(String text) {
25 | return of(text);
26 | }
27 | }
28 |
29 | public TimeZoneToken(ZoneId value) {
30 | this.value = value;
31 | }
32 |
33 | public static TimeZoneToken of(String text) {
34 | TimeZoneToken token;
35 |
36 | try {
37 | token = new TimeZoneToken(ZoneId.of(text));
38 | } catch (Exception e) {
39 | throw new DateCalcException("Invalid time zone specified: " + text);
40 | }
41 |
42 | return token;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/Token.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.datecalc.parser.token;
7 |
8 | /**
9 | *
10 | * @author jason
11 | * @param
12 | */
13 | public abstract class Token {
14 | protected T value;
15 |
16 | public interface Info {
17 | String getRegex();
18 | Token getToken(String text);
19 | }
20 |
21 | public T getValue() {
22 | return value;
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return this.getClass().getSimpleName() + ": " + getValue();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module datecalc.lib {
2 | exports com.steeplesoft.datecalc;
3 | }
--------------------------------------------------------------------------------
/Chapter04/datecalc-lib/src/test/java/com/steeplesoft/datecalc/DateCalculatorTest.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.datecalc;
2 |
3 | import com.steeplesoft.datecalc.DateCalculator;
4 | import com.steeplesoft.datecalc.DateCalculatorResult;
5 | import java.time.LocalTime;
6 | import java.time.Period;
7 | import org.testng.Assert;
8 | import org.testng.annotations.Test;
9 |
10 | /**
11 | *
12 | * @author jason
13 | */
14 | public class DateCalculatorTest {
15 | private final DateCalculator dc = new DateCalculator();
16 |
17 | @Test
18 | public void testDateMath() {
19 | final String expression = "today + 2 weeks 3 days";
20 | DateCalculatorResult result = dc.calculate(expression);
21 | Assert.assertNotNull(result.getDate().get(), "'" + expression + "' should have returned a result.");
22 | }
23 |
24 | @Test
25 | public void testDateDiff() {
26 | final String expression = "2016/07/04 - 1776/07/04";
27 | DateCalculatorResult result = dc.calculate(expression);
28 | Assert.assertEquals(result.getPeriod().get(), Period.of(240,0,0), "'" + expression + "' should...");
29 | }
30 |
31 | @Test
32 | public void timeMath() {
33 | final String expression = "12:37 + 42 m";
34 | DateCalculatorResult result = dc.calculate(expression);
35 | Assert.assertEquals(result.getTime().get(), LocalTime.parse("13:19"));
36 | }
37 |
38 | @Test
39 | public void timeDiff() {
40 | final String expression = "12:37 - 7:15";
41 | DateCalculatorResult result = dc.calculate(expression);
42 | Assert.assertEquals(result.getDuration().get().toHoursPart(), 5);
43 | Assert.assertEquals(result.getDuration().get().toMinutesPart(), 22);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Chapter05/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
--------------------------------------------------------------------------------
/Chapter05/api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | sunago-master
7 | 1.0-SNAPSHOT
8 |
9 | sunago-api
10 | Sunago - API
11 | jar
12 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/SunagoPrefsKeys.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public enum SunagoPrefsKeys {
8 | ITEM_COUNT("itemCount");
9 | private final String key;
10 |
11 | SunagoPrefsKeys(String key) {
12 | this.key = key;
13 | }
14 |
15 | public String getKey() {
16 | return "sunago." + key;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/SunagoUtil.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import com.steeplesoft.sunago.api.SunagoPreferences;
4 | import java.util.Iterator;
5 | import java.util.ServiceLoader;
6 |
7 | /**
8 | *
9 | * @author jason
10 | */
11 | public class SunagoUtil {
12 | private static SunagoPreferences preferences;
13 |
14 | public static synchronized SunagoPreferences getSunagoPreferences() {
15 | if (preferences == null) {
16 | ServiceLoader spLoader = ServiceLoader.load(SunagoPreferences.class);
17 | Iterator iterator = spLoader.iterator();
18 | preferences = iterator.hasNext() ? iterator.next() : null;
19 | }
20 | return preferences;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SocialMediaClient.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public interface SocialMediaClient {
10 | void authenticateUser(String token, String tokenSecret);
11 | String getAuthorizationUrl();
12 | List extends SocialMediaItem> getItems();
13 | boolean isAuthenticated();
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SocialMediaItem.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | import java.io.Serializable;
4 | import java.util.Date;
5 |
6 | public interface SocialMediaItem extends Serializable {
7 | String getProvider();
8 | String getTitle();
9 | String getBody();
10 | String getUrl();
11 | String getImage();
12 | Date getTimestamp();
13 | }
14 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SunagoPreferences.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public interface SunagoPreferences {
8 | String getPreference(String key);
9 | String getPreference(String key, String defaultValue);
10 | Integer getPreference(String key, Integer defaultValue);
11 | void putPreference(String key, String value);
12 | void putPreference(String key, Integer value);
13 | }
14 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/api/fx/SelectableItem.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api.fx;
2 |
3 | import javafx.beans.property.SimpleBooleanProperty;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public abstract class SelectableItem {
10 | private final SimpleBooleanProperty selected = new SimpleBooleanProperty(false);
11 | private final T item;
12 | public SelectableItem(T item) {
13 | this.item = item;
14 | }
15 | public T getItem() {
16 | return item;
17 | }
18 | public SimpleBooleanProperty getSelected() {
19 | return selected;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter05/api/src/main/java/com/steeplesoft/sunago/api/fx/SocialMediaPreferencesController.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api.fx;
2 |
3 | import javafx.scene.control.Tab;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public abstract class SocialMediaPreferencesController {
10 | public abstract Tab getTab();
11 | public abstract void savePreferences();
12 | }
--------------------------------------------------------------------------------
/Chapter05/app/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | sunago-master
7 | 1.0-SNAPSHOT
8 |
9 | sunago-app
10 | Sunago - App
11 | jar
12 |
13 |
14 |
15 | ${project.groupId}
16 | sunago-api
17 | ${project.version}
18 |
19 |
20 |
21 |
22 |
23 | maven-shade-plugin
24 | ${plugin.shade}
25 |
26 |
27 | package
28 |
29 | shade
30 |
31 |
32 |
33 |
34 | com.steeplesoft.sunago.app.Sunago
35 |
36 |
37 |
38 |
39 | ${project.groupId}:twitter
40 | ${project.groupId}:instagram
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/java/com/steeplesoft/sunago/app/Sunago.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.app;
2 |
3 | import java.io.File;
4 | import java.net.MalformedURLException;
5 | import java.net.URL;
6 | import java.net.URLClassLoader;
7 | import java.util.Arrays;
8 | import java.util.logging.Level;
9 | import java.util.logging.Logger;
10 | import javafx.application.Application;
11 | import static javafx.application.Application.launch;
12 | import javafx.fxml.FXMLLoader;
13 | import javafx.scene.Parent;
14 | import javafx.scene.Scene;
15 | import javafx.stage.Stage;
16 |
17 | /**
18 | *
19 | * @author jason
20 | */
21 | public class Sunago extends Application {
22 |
23 | public Sunago() throws Exception {
24 | super();
25 | updateClassLoader();
26 | }
27 |
28 | @Override
29 | public void start(Stage stage) throws Exception {
30 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/sunago.fxml"));
31 |
32 | Scene scene = new Scene(root);
33 | scene.getStylesheets().add("/styles/styles.css");
34 |
35 | stage.setTitle("Sunago");
36 | stage.setScene(scene);
37 | stage.show();
38 | }
39 |
40 | public static void main(String[] args) {
41 | launch(args);
42 | }
43 |
44 | private void updateClassLoader() {
45 | final File[] jars = getFiles();
46 | if (jars != null) {
47 | URL[] urls = new URL[jars.length];
48 | int index = 0;
49 | for (File jar : jars) {
50 | try {
51 | urls[index++] = jar.toURI().toURL();
52 | } catch (MalformedURLException ex) {
53 | Logger.getLogger(Sunago.class.getName()).log(Level.SEVERE, null, ex);
54 | }
55 | }
56 | Thread.currentThread().setContextClassLoader(URLClassLoader.newInstance(urls));
57 | }
58 | }
59 |
60 | private File[] getFiles() {
61 | String pluginDir = System.getProperty("user.home") + "/.sunago";
62 | return new File(pluginDir).listFiles(file -> file.isFile() && file.getName().toLowerCase().endsWith(".jar"));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/java/com/steeplesoft/sunago/app/SunagoPreferencesImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.sunago.app;
7 |
8 | import com.steeplesoft.sunago.api.SunagoPreferences;
9 | import java.util.prefs.Preferences;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | public class SunagoPreferencesImpl implements SunagoPreferences {
16 | private final Preferences prefs = Preferences.userRoot()
17 | .node(SunagoPreferencesImpl.class.getPackage().getName());
18 |
19 | @Override
20 | public String getPreference(String key) {
21 | return prefs.get(key, null);
22 | }
23 |
24 | @Override
25 | public String getPreference(String key, String defaultValue) {
26 | return prefs.get(key, defaultValue);
27 | }
28 |
29 | @Override
30 | public Integer getPreference(String key, Integer defaultValue) {
31 | return prefs.getInt(key, defaultValue);
32 | }
33 |
34 | @Override
35 | public void putPreference(String key, String value) {
36 | prefs.put(key, value);
37 | }
38 |
39 | @Override
40 | public void putPreference(String key, Integer value) {
41 | prefs.putInt(key, value);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/java/com/steeplesoft/sunago/app/SunagoProperties.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.app;
2 |
3 | import com.steeplesoft.sunago.api.SunagoPreferences;
4 | import java.io.File;
5 | import java.io.FileInputStream;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.util.Properties;
10 | import java.util.logging.Level;
11 | import java.util.logging.Logger;
12 |
13 | /**
14 | *
15 | * @author jason
16 | */
17 | public class SunagoProperties implements SunagoPreferences {
18 | private Properties props = new Properties();
19 | private final String FILE = System.getProperty("user.home") + File.separator + ".sunago.properties";
20 |
21 | public SunagoProperties() {
22 | try (InputStream input = new FileInputStream(FILE)) {
23 | props.load(input);
24 | } catch (IOException ex) {
25 | }
26 | }
27 |
28 | @Override
29 | public String getPreference(String key) {
30 | return props.getProperty(key);
31 | }
32 |
33 | @Override
34 | public String getPreference(String key, String defaultValue) {
35 | String value = props.getProperty(key);
36 | return (value == null) ? defaultValue : value;
37 | }
38 |
39 | @Override
40 | public Integer getPreference(String key, Integer defaultValue) {
41 | String value = props.getProperty(key);
42 | return (value == null) ? defaultValue : Integer.parseInt(value);
43 | }
44 |
45 | @Override
46 | public void putPreference(String key, String value) {
47 | props.put(key, value);
48 | store();
49 | }
50 |
51 | @Override
52 | public void putPreference(String key, Integer value) {
53 | if (value != null) {
54 | putPreference(key, value.toString());
55 | }
56 | }
57 |
58 | private void store() {
59 | try (FileOutputStream output = new FileOutputStream(FILE)) {
60 | props.store(output, null);
61 | } catch (IOException e) {
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.SunagoPreferences:
--------------------------------------------------------------------------------
1 | #com.steeplesoft.sunago.app.SunagoPreferencesImpl
2 | com.steeplesoft.sunago.app.SunagoProperties
3 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/fxml/login.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/fxml/prefs.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/fxml/sunago.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/images/reload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter05/app/src/main/resources/images/reload.png
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/images/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter05/app/src/main/resources/images/settings.png
--------------------------------------------------------------------------------
/Chapter05/app/src/main/resources/styles/styles.css:
--------------------------------------------------------------------------------
1 | .button {
2 | -fx-font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/Chapter05/instagram/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | sunago-master
7 | 1.0-SNAPSHOT
8 |
9 | sunago-instagram
10 | Sunago - Instagram
11 | jar
12 |
13 |
14 | ${project.groupId}
15 | sunago-api
16 | ${project.version}
17 | provided
18 |
19 |
20 | com.sachinhandiekar
21 | jInstagram
22 | 1.1.9
23 |
24 |
25 |
26 |
27 |
28 | maven-shade-plugin
29 | ${plugin.shade}
30 |
31 |
32 | package
33 |
34 | shade
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Chapter05/instagram/src/main/java/com/steeplesoft/sunago/instagram/InstagramPrefsKeys.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.sunago.instagram;
7 |
8 |
9 | /**
10 | *
11 | * @author jason
12 | */
13 | public enum InstagramPrefsKeys {
14 | TOKEN("token"),
15 | TOKEN_SECRET("tokenSecret");
16 | private final String key;
17 |
18 | InstagramPrefsKeys(String key) {
19 | this.key = key;
20 | }
21 |
22 | public String getKey() {
23 | return "instagram." + key;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Chapter05/instagram/src/main/java/com/steeplesoft/sunago/instagram/Photo.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.instagram;
2 |
3 | import com.steeplesoft.sunago.api.SocialMediaItem;
4 | import java.util.Date;
5 | import org.jinstagram.entity.users.feed.MediaFeedData;
6 |
7 | /**
8 | *
9 | * @author jason
10 | */
11 | public class Photo implements SocialMediaItem {
12 | private final MediaFeedData data;
13 | public Photo(MediaFeedData data) {
14 | this.data = data;
15 | }
16 |
17 | public String getId() {
18 | return data.getId();
19 | }
20 |
21 | @Override
22 | public String getProvider() {
23 | return "Instagram";
24 | }
25 |
26 | @Override
27 | public String getTitle() {
28 | return "";
29 | }
30 |
31 | @Override
32 | public String getBody() {
33 |
34 | return String.format("%s: %s (%s)", data.getUser().getFullName(),
35 | data.getCaption().getText(),getTimestamp().toString());
36 | }
37 |
38 | @Override
39 | public String getUrl() {
40 | return data.getLink();
41 | }
42 |
43 | @Override
44 | public Date getTimestamp() {
45 | return new Date(Long.parseLong(data.getCreatedTime())*1000);
46 | }
47 |
48 | @Override
49 | public String getImage() {
50 | return data.getImages().getThumbnail().getImageUrl();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Chapter05/instagram/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.SocialMediaClient:
--------------------------------------------------------------------------------
1 | com.steeplesoft.sunago.instagram.InstagramClient
--------------------------------------------------------------------------------
/Chapter05/instagram/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.fx.SocialMediaPreferencesController:
--------------------------------------------------------------------------------
1 | com.steeplesoft.sunago.instagram.fx.InstagramPreferencesController
2 |
--------------------------------------------------------------------------------
/Chapter05/instagram/src/main/resources/com/steeplesoft/j9bp/sunago/instagram/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter05/instagram/src/main/resources/com/steeplesoft/j9bp/sunago/instagram/icon.png
--------------------------------------------------------------------------------
/Chapter05/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.steeplesoft
5 | sunago-master
6 | 1.0-SNAPSHOT
7 | Sunago - Master
8 | pom
9 |
10 |
11 | UTF-8
12 | 2.4.3
13 |
14 |
15 |
16 | api
17 | twitter
18 | instagram
19 | app
20 |
21 |
22 |
23 |
24 | org.testng
25 | testng
26 | 6.10
27 |
28 |
29 |
30 |
31 |
32 | org.apache.maven.plugins
33 | maven-compiler-plugin
34 | 3.6.1
35 |
36 | 1.8
37 | 1.8
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Chapter05/twitter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | sunago-master
7 | 1.0-SNAPSHOT
8 |
9 | sunago-twitter
10 | Sunago - Twitter
11 | jar
12 |
13 |
14 | ${project.groupId}
15 | sunago-api
16 | ${project.version}
17 | provided
18 |
19 |
20 | org.twitter4j
21 | twitter4j-core
22 | [4.0,)
23 |
24 |
25 |
26 |
27 |
28 | maven-shade-plugin
29 | ${plugin.shade}
30 |
31 |
32 | package
33 |
34 | shade
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/java/com/steeplesoft/sunago/twitter/MessageBundle.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | import java.util.Locale;
4 | import java.util.ResourceBundle;
5 |
6 | /**
7 | *
8 | * @author jason
9 | */
10 | public class MessageBundle {
11 | ResourceBundle messages = ResourceBundle.getBundle("Messages", Locale.getDefault());
12 |
13 | private MessageBundle() {
14 |
15 | }
16 |
17 | public final String getString(String key) {
18 | return messages.getString(key);
19 | }
20 |
21 | private static class LazyHolder {
22 | private static final MessageBundle INSTANCE = new MessageBundle();
23 | }
24 |
25 | public static MessageBundle getInstance() {
26 | return LazyHolder.INSTANCE;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/java/com/steeplesoft/sunago/twitter/Tweet.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | import com.steeplesoft.sunago.api.SocialMediaItem;
4 | import java.util.Date;
5 | import twitter4j.MediaEntity;
6 | import twitter4j.Status;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class Tweet implements SocialMediaItem {
13 |
14 | private final Status status;
15 | private final String url;
16 | private final String body;
17 |
18 | public Tweet(Status status) {
19 | this.status = status;
20 | body = String.format("@%s: %s (%s)", status.getUser().getScreenName(),
21 | status.getText(), status.getCreatedAt().toString());
22 | url = String.format("https://twitter.com/%s/status/%d",
23 | status.getUser().getScreenName(), status.getId());
24 | }
25 |
26 | @Override
27 | public String getProvider() {
28 | return "Twitter";
29 | }
30 |
31 | @Override
32 | public String getTitle() {
33 | return null;
34 | }
35 |
36 | @Override
37 | public String getBody() {
38 | return body;
39 | }
40 |
41 | @Override
42 | public String getUrl() {
43 | return url;
44 | }
45 |
46 | @Override
47 | public Date getTimestamp() {
48 | return status.getCreatedAt();
49 | }
50 |
51 | @Override
52 | public String getImage() {
53 | MediaEntity[] mediaEntities = status.getMediaEntities();
54 | if (mediaEntities.length > 0) {
55 | return mediaEntities[0].getMediaURLHttps();
56 | } else {
57 | Status retweetedStatus = status.getRetweetedStatus();
58 | if (retweetedStatus != null) {
59 | if (retweetedStatus.getMediaEntities().length > 0) {
60 | return retweetedStatus.getMediaEntities()[0].getMediaURLHttps();
61 | }
62 | }
63 | }
64 | return null;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/java/com/steeplesoft/sunago/twitter/TwitterPrefsKeys.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public enum TwitterPrefsKeys {
8 | HOME_TIMELINE("showHomeTimeline"),
9 | SELECTED_LISTS("selectedLists"),
10 | SINCE_ID("sinceId"),
11 | TOKEN("token"),
12 | TOKEN_SECRET("tokenSecret");
13 | private final String key;
14 |
15 | TwitterPrefsKeys(String key) {
16 | this.key = key;
17 | }
18 |
19 | public String getKey() {
20 | return "twitter." + key;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/java/com/steeplesoft/sunago/twitter/fx/SelectableUserList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.sunago.twitter.fx;
7 |
8 | import com.steeplesoft.sunago.api.fx.SelectableItem;
9 | import twitter4j.UserList;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | public class SelectableUserList extends SelectableItem {
16 |
17 | public SelectableUserList(UserList item) {
18 | super(item);
19 | }
20 |
21 | @Override
22 | public String toString() {
23 | return getItem().getSlug();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.SocialMediaClient:
--------------------------------------------------------------------------------
1 | com.steeplesoft.sunago.twitter.TwitterClient
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.fx.SocialMediaPreferencesController:
--------------------------------------------------------------------------------
1 | com.steeplesoft.sunago.twitter.fx.TwitterPreferencesController
2 |
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/resources/Messages.properties:
--------------------------------------------------------------------------------
1 | homeTimelineCB=Include the home timeline
2 | userListLabel=User lists to include
3 | connect=Connect
4 | twitter=Twitter
--------------------------------------------------------------------------------
/Chapter05/twitter/src/main/resources/com/steeplesoft/sunago/twitter/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter05/twitter/src/main/resources/com/steeplesoft/sunago/twitter/icon.png
--------------------------------------------------------------------------------
/Chapter06/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/Chapter06/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Chapter06/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '25.0.0'
6 | defaultConfig {
7 | applicationId "com.steeplesoft.sunago"
8 | minSdkVersion 21
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | }
18 | lintOptions {
19 | abortOnError false
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | }
29 |
30 | dependencies {
31 | compile fileTree(dir: 'libs', include: ['*.jar'])
32 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
33 | exclude group: 'com.android.support', module: 'support-annotations'
34 | })
35 | compile 'com.android.support:appcompat-v7:25.1.0'
36 | compile 'com.android.support:support-v13:25.1.0'
37 | compile 'com.android.support:design:25.1.0'
38 | compile 'org.twitter4j:twitter4j-core:4.0.5'
39 | compile 'com.sachinhandiekar:jInstagram:1.1.8'
40 |
41 | testCompile 'junit:junit:4.12'
42 | }
43 |
--------------------------------------------------------------------------------
/Chapter06/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\jason\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -dontwarn java.lang.invoke.*
19 | -dontwarn twitter4j.**
20 | -dontwarn javax.management.**
21 | -dontwarn javax.xml.**
22 | -dontwarn org.apache.**
23 | -dontwarn org.slf4j.**
--------------------------------------------------------------------------------
/Chapter06/app/src/androidTest/java/com/steeplesoft/sunago/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.steeplesoft.sunago", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/PluginServiceConnection.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import android.content.ComponentName;
4 | import android.content.ServiceConnection;
5 | import android.os.IBinder;
6 | import android.util.Log;
7 |
8 | public class PluginServiceConnection implements ServiceConnection {
9 | public void onServiceConnected(ComponentName className, IBinder boundService) {
10 | }
11 |
12 | public void onServiceDisconnected(ComponentName className) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/Sunago.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | public class Sunago extends Application {
7 | private static Context context;
8 |
9 | public void onCreate() {
10 | super.onCreate();
11 | Sunago.context = getApplicationContext();
12 | }
13 |
14 | public static Context getAppContext() {
15 | return Sunago.context;
16 | }}
17 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/SunagoUtil.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | /**
7 | * Created by jason on 12/26/2016.
8 | */
9 |
10 | public class SunagoUtil {
11 | private static SharedPreferences prefs;
12 |
13 | public static SharedPreferences getPreferences() {
14 | if (prefs == null) {
15 | prefs = Sunago.getAppContext().getSharedPreferences(Sunago.getAppContext().getPackageName(), Context.MODE_PRIVATE);
16 | }
17 | return prefs;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/WebLoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.support.v7.widget.Toolbar;
10 | import android.webkit.WebView;
11 | import android.webkit.WebViewClient;
12 |
13 | public class WebLoginActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_web_view);
19 | setTitle("Login");
20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
21 | setSupportActionBar(toolbar);
22 | Intent intent = getIntent();
23 | final String url = intent.getStringExtra("url");
24 | final String queryParam = intent.getStringExtra("queryParam");
25 |
26 | WebView webView = (WebView)findViewById(R.id.webView);
27 | final WebViewClient client = new LoginWebViewClient(queryParam);
28 | webView.setWebViewClient(client);
29 | webView.loadUrl(url);
30 | }
31 |
32 | private class LoginWebViewClient extends WebViewClient {
33 | private String queryParam;
34 |
35 | public LoginWebViewClient(String queryParam) {
36 | this.queryParam = queryParam;
37 | }
38 |
39 | @Override
40 | public void onPageStarted(WebView view, String url, Bitmap favicon) {
41 | final Uri uri = Uri.parse(url);
42 | final String value = uri.getQueryParameter(queryParam);
43 | if (value != null) {
44 | Intent resultIntent = new Intent();
45 | for (String name : uri.getQueryParameterNames()) {
46 | resultIntent.putExtra(name, uri.getQueryParameter(name));
47 | }
48 | setResult(Activity.RESULT_OK, resultIntent);
49 | finish();
50 | }
51 | super.onPageStarted(view, url, favicon);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/api/MessageHandler.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | /**
7 | * Created by jason on 12/29/2016.
8 | */
9 | public class MessageHandler extends Handler {
10 | @Override
11 | public void handleMessage(Message msg) {
12 | super.handleMessage(msg);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/api/SocialMediaClient.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public interface SocialMediaClient {
10 | void authenticateUser(String token, String tokenSecret);
11 | String getAuthorizationUrl();
12 | List extends SocialMediaItem> getItems();
13 | boolean isAuthenticated();
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/api/SocialMediaItem.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.api;
2 |
3 | import java.io.Serializable;
4 | import java.util.Date;
5 |
6 | public interface SocialMediaItem extends Serializable {
7 | String getProvider();
8 | String getTitle();
9 | String getBody();
10 | String getUrl();
11 | String getImage();
12 | Date getTimestamp();
13 | }
14 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/data/SunagoOpenHelper.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.data;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 | import android.database.sqlite.SQLiteStatement;
7 |
8 | public class SunagoOpenHelper extends SQLiteOpenHelper {
9 | private static final String SQL_CREATE_MAIN = "CREATE TABLE items (" +
10 | " _id INTEGER PRIMARY KEY, " +
11 | " provider TEXT, " +
12 | " title TEXT, " +
13 | " body TEXT, " +
14 | " url TEXT, " +
15 | " image TEXT, " +
16 | " timestamp TEXT )";
17 |
18 | public SunagoOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
19 | super(context, name, factory, version);
20 | }
21 |
22 | @Override
23 | public void onCreate(SQLiteDatabase db) {
24 | db.execSQL(SQL_CREATE_MAIN);
25 | }
26 |
27 | @Override
28 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/instagram/Photo.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.instagram;
2 |
3 | import com.steeplesoft.sunago.api.SocialMediaItem;
4 |
5 | import java.util.Date;
6 | import org.jinstagram.entity.users.feed.MediaFeedData;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class Photo implements SocialMediaItem {
13 | private final MediaFeedData data;
14 | public Photo(MediaFeedData data) {
15 | this.data = data;
16 | }
17 |
18 | public String getId() {
19 | return data.getId();
20 | }
21 |
22 | @Override
23 | public String getProvider() {
24 | return "Instagram";
25 | }
26 |
27 | @Override
28 | public String getTitle() {
29 | return "";
30 | }
31 |
32 | @Override
33 | public String getBody() {
34 |
35 | return String.format("%s: %s (%s)", data.getUser().getFullName(),
36 | data.getCaption().getText(),getTimestamp().toString());
37 | }
38 |
39 | @Override
40 | public String getUrl() {
41 | return data.getLink();
42 | }
43 |
44 | @Override
45 | public Date getTimestamp() {
46 | return new Date(Long.parseLong(data.getCreatedTime())*1000);
47 | }
48 |
49 | @Override
50 | public String getImage() {
51 | return data.getImages().getThumbnail().getImageUrl();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/twitter/DataLoadAsyncTask.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.os.AsyncTask;
6 | import android.util.Log;
7 |
8 | import com.steeplesoft.sunago.MainActivity;
9 | import com.steeplesoft.sunago.Sunago;
10 | import com.steeplesoft.sunago.api.SocialMediaItem;
11 | import com.steeplesoft.sunago.data.SunagoContentProvider;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public abstract class DataLoadAsyncTask extends AsyncTask> {
17 |
18 | protected List processItems(List extends SocialMediaItem> items) {
19 | List values = new ArrayList<>();
20 | for (SocialMediaItem item : items) {
21 | ContentValues cv = new ContentValues();
22 | cv.put(SunagoContentProvider.BODY, item.getBody());
23 | cv.put(SunagoContentProvider.URL, item.getUrl());
24 | cv.put(SunagoContentProvider.IMAGE, item.getImage());
25 | cv.put(SunagoContentProvider.PROVIDER, item.getProvider());
26 | cv.put(SunagoContentProvider.TITLE, item.getTitle());
27 | cv.put(SunagoContentProvider.TIMESTAMP, item.getTimestamp().getTime());
28 | values.add(cv);
29 | }
30 | return values;
31 | }
32 |
33 | @Override
34 | protected void onPostExecute(List values) {
35 | Log.i(MainActivity.LOG_TAG, "Inserting " + values.size() + " tweets.");
36 | Sunago.getAppContext().getContentResolver().bulkInsert(SunagoContentProvider.CONTENT_URI,
37 | values.toArray(new ContentValues[0]));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/twitter/Tweet.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | import com.steeplesoft.sunago.api.SocialMediaItem;
4 |
5 | import java.util.Date;
6 | import twitter4j.MediaEntity;
7 | import twitter4j.Status;
8 |
9 | /**
10 | *
11 | * @author jason
12 | */
13 | public class Tweet implements SocialMediaItem {
14 |
15 | private final Status status;
16 | private final String url;
17 | private final String body;
18 |
19 | public Tweet(Status status) {
20 | this.status = status;
21 | body = String.format("@%s: %s (%s)", status.getUser().getScreenName(),
22 | status.getText(), status.getCreatedAt().toString());
23 | url = String.format("https://twitter.com/%s/status/%d",
24 | status.getUser().getScreenName(), status.getId());
25 | }
26 |
27 | @Override
28 | public String getProvider() {
29 | return "Twitter";
30 | }
31 |
32 | @Override
33 | public String getTitle() {
34 | return null;
35 | }
36 |
37 | @Override
38 | public String getBody() {
39 | return body;
40 | }
41 |
42 | @Override
43 | public String getUrl() {
44 | return url;
45 | }
46 |
47 | @Override
48 | public Date getTimestamp() {
49 | return status.getCreatedAt();
50 | }
51 |
52 | @Override
53 | public String getImage() {
54 | MediaEntity[] mediaEntities = status.getMediaEntities();
55 | if (mediaEntities.length > 0) {
56 | return mediaEntities[0].getMediaURLHttps();
57 | } else {
58 | Status retweetedStatus = status.getRetweetedStatus();
59 | if (retweetedStatus != null) {
60 | if (retweetedStatus.getMediaEntities().length > 0) {
61 | return retweetedStatus.getMediaEntities()[0].getMediaURLHttps();
62 | }
63 | }
64 | }
65 | return null;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/java/com/steeplesoft/sunago/twitter/TwitterService.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.sunago.twitter;
2 |
3 | import android.app.IntentService;
4 | import android.content.AsyncQueryHandler;
5 | import android.content.BroadcastReceiver;
6 | import android.content.ContentValues;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.IntentFilter;
10 | import android.os.AsyncTask;
11 | import android.os.IBinder;
12 | import android.util.Log;
13 |
14 | import com.steeplesoft.sunago.MainActivity;
15 | import com.steeplesoft.sunago.R;
16 | import com.steeplesoft.sunago.SunagoUtil;
17 | import com.steeplesoft.sunago.api.SocialMediaItem;
18 | import com.steeplesoft.sunago.data.SunagoContentProvider;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | public class TwitterService extends IntentService {
24 | private BroadcastReceiver receiver;
25 |
26 | public TwitterService() {
27 | super("TwitterService");
28 | }
29 |
30 | @Override
31 | public IBinder onBind(Intent intent) {
32 | receiver = new TwitterServiceReceiver();
33 | registerReceiver(receiver, new IntentFilter("sunago.service"));
34 | return null;
35 | }
36 |
37 | @Override
38 | public boolean onUnbind(Intent intent) {
39 | unregisterReceiver(receiver);
40 | return super.onUnbind(intent);
41 | }
42 |
43 | @Override
44 | protected void onHandleIntent(Intent intent) {
45 | }
46 |
47 | private class TwitterUpdatesAsyncTask extends DataLoadAsyncTask {
48 | @Override
49 | protected List doInBackground(Void... contexts) {
50 | return processItems(TwitterClient.instance().getItems());
51 | }
52 | }
53 |
54 | private class TwitterServiceReceiver extends BroadcastReceiver {
55 | @Override
56 | public void onReceive(Context context, Intent intent) {
57 | if ("REFRESH".equals(intent.getStringExtra("message"))) {
58 | if (SunagoUtil.getPreferences().getBoolean(getString(R.string.twitter_authd), false)) {
59 | new TwitterUpdatesAsyncTask().execute();
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/activity_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/activity_web_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/content_web_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/fragment_instagram_preferences.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/fragment_preferences.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/fragment_twitter_preferences.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
22 |
23 |
28 |
29 |
34 |
35 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/social_media_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
23 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/layout/user_list_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
9 |
10 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/menu/menu_preferences.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter06/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter06/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter06/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter06/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter06/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3f51b5
4 | #303f9f
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 | 8dp
7 |
8 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Sunago - Social Media Aggregrator
3 | Settings
4 | Refresh
5 | Sunago Preferences
6 |
7 |
8 | twitter_oauth_token
9 | twitter_oauth_token_secret
10 | twitter_authd
11 | twitter_request_token
12 | twitter_request_token_secret
13 | twitter_selected_lists
14 | twitter_show_home_timeline
15 | item_count
16 | instagram_token
17 | instagram_token_secret
18 | instagram_authd
19 | Logout
20 | Login
21 | WebViewActivity
22 |
23 |
--------------------------------------------------------------------------------
/Chapter06/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Chapter06/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:3.0.0-alpha5'
8 | }
9 | }
10 |
11 | allprojects {
12 | repositories {
13 | jcenter()
14 | mavenLocal()
15 | mavenCentral()
16 | }
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter06/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/Chapter06/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/Chapter07/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
--------------------------------------------------------------------------------
/Chapter07/cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.steeplesoft
8 | mailfilter-master
9 | 1.0-SNAPSHOT
10 |
11 |
12 | MailFilter - CLI
13 | mailfilter-cli
14 | jar
15 |
16 |
17 |
18 | com.sun.mail
19 | javax.mail
20 | 1.5.6
21 |
22 |
23 | com.fasterxml.jackson.core
24 | jackson-databind
25 | 2.8.5
26 |
27 |
28 |
29 |
30 | org.hibernate
31 | hibernate-validator
32 | 5.3.4.Final
33 |
34 |
35 | javax.el
36 | javax.el-api
37 | 2.2.4
38 |
39 |
40 | org.glassfish.web
41 | javax.el
42 | 2.2.4
43 |
44 |
45 |
46 | com.icegreen
47 | greenmail
48 | 1.5.2
49 | test
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/MailFilter.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter;
2 |
3 | import com.steeplesoft.mailfilter.model.Account;
4 | import java.io.IOException;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 | import javax.mail.MessagingException;
8 |
9 | public class MailFilter {
10 | private int moved;
11 | private int deleted;
12 | private final String fileName;
13 |
14 | /**
15 | * @param args the command line arguments
16 | */
17 | public static void main(String... args) {
18 | try {
19 | final MailFilter mailFilter = new MailFilter(args.length > 0 ? args[1] : null);
20 | mailFilter.run();
21 | System.out.println("\tDeleted count: " + mailFilter.getDeleted());
22 | System.out.println("\tMove count: " + mailFilter.getMoved());
23 | } catch (Exception e) {
24 | System.err.println(e.getLocalizedMessage());
25 | }
26 | }
27 |
28 | public MailFilter() {
29 | this.fileName = null;
30 | }
31 |
32 | public MailFilter(String fileName) {
33 | this.fileName = fileName;
34 | }
35 |
36 | public void run() {
37 | try {
38 | AccountService service = new AccountService(fileName);
39 |
40 | for (Account account : service.getAccounts()) {
41 | AccountProcessor processor = new AccountProcessor(account);
42 | processor.process();
43 | deleted += processor.getDeleteCount();
44 | moved += processor.getMoveCount();
45 | }
46 | } catch (MessagingException ex) {
47 | Logger.getLogger(MailFilter.class.getName()).log(Level.SEVERE, null, ex);
48 | }
49 | }
50 |
51 | public int getMoved() {
52 | return moved;
53 | }
54 |
55 | public int getDeleted() {
56 | return deleted;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/exceptions/AccountValidationException.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.exceptions;
2 |
3 | import com.steeplesoft.mailfilter.model.Account;
4 | import java.util.Set;
5 | import javax.validation.ConstraintViolation;
6 |
7 | /**
8 | *
9 | * @author jason
10 | */
11 | public class AccountValidationException extends RuntimeException {
12 | private final Set> violations;
13 |
14 | public AccountValidationException(Set> violations) {
15 | super();
16 | this.violations = violations;
17 | }
18 |
19 | @Override
20 | public String getMessage() {
21 | StringBuilder message = new StringBuilder("Account validation error(s) occurred:");
22 | for (ConstraintViolation violation : violations) {
23 | message.append("\n\t").append(violation.getMessage());
24 | }
25 |
26 | return message.toString();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/exceptions/MailFilterException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.mailfilter.exceptions;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class MailFilterException extends RuntimeException {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/exceptions/RuleValidationException.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.exceptions;
2 |
3 | import com.steeplesoft.mailfilter.model.Rule;
4 | import java.util.Set;
5 | import javax.validation.ConstraintViolation;
6 |
7 | /**
8 | *
9 | * @author jason
10 | */
11 | public class RuleValidationException extends MailFilterException {
12 | private final Set> violations;
13 |
14 | public RuleValidationException(Set> violations) {
15 | super();
16 | this.violations = violations;
17 | }
18 |
19 | @Override
20 | public String getMessage() {
21 | StringBuilder message = new StringBuilder("Rule validation error(s) occurred:");
22 | for (ConstraintViolation violation : violations) {
23 | message.append("\n\t").append(violation.getMessage());
24 | }
25 |
26 | return message.toString();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/model/RuleType.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.model;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public enum RuleType {
8 | DELETE, MOVE;
9 |
10 | public static RuleType getRuleType(String type) {
11 | switch(type.toLowerCase()) {
12 | case "delete" : return DELETE;
13 | case "move" : return MOVE;
14 | default : return null;
15 | // throw new IllegalArgumentException("Invalid rule type specified: " + type);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/main/java/com/steeplesoft/mailfilter/model/validation/ValidRule.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.model.validation;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 | import javax.validation.Constraint;
9 | import javax.validation.Payload;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.FIELD})
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @Constraint(validatedBy = ValidRuleValidator.class)
18 | @Documented
19 | public @interface ValidRule {
20 | String message() default "Validation errors";
21 | Class>[] groups() default {};
22 | Class extends Payload>[] payload() default {};
23 | }
--------------------------------------------------------------------------------
/Chapter07/cli/src/test/java/com/steeplesoft/mailfilter/test/RuleTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.mailfilter.test;
7 |
8 | import com.steeplesoft.mailfilter.model.Rule;
9 | import java.util.HashSet;
10 | import java.util.Set;
11 | import javax.mail.Message;
12 | import javax.mail.search.FromStringTerm;
13 | import javax.mail.search.OrTerm;
14 | import javax.mail.search.RecipientStringTerm;
15 | import javax.mail.search.SearchTerm;
16 | import org.testng.Assert;
17 | import org.testng.annotations.Test;
18 |
19 | /**
20 | *
21 | * @author jason
22 | */
23 | public class RuleTest {
24 |
25 | @Test
26 | public void multipleFieldsShouldReturnOrTerm() {
27 | final Set fields = new HashSet();
28 | fields.add("to");
29 | fields.add("from");
30 | fields.add("cc");
31 |
32 | Rule rule = Rule.create()
33 | .sourceFolder("INBOX")
34 | .matchingText("testText")
35 | .fields(fields);
36 | SearchTerm term = rule.getSearchTerm();
37 | Assert.assertTrue(term instanceof OrTerm);
38 | SearchTerm[] terms = ((OrTerm) term).getTerms();
39 | for (SearchTerm t : terms) {
40 | Assert.assertTrue(t instanceof FromStringTerm
41 | || (t instanceof RecipientStringTerm && ((RecipientStringTerm) t).getRecipientType() == Message.RecipientType.CC)
42 | || (t instanceof RecipientStringTerm && ((RecipientStringTerm) t).getRecipientType() == Message.RecipientType.TO));
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Chapter07/cli/src/test/resources/rules.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "serverName": "localhost",
4 | "serverPort": "3025",
5 | "useSsl": false,
6 | "userName": "to@localhost.com",
7 | "password": "password",
8 | "rules": [
9 | {"destFolder": "Ads","matchingText": "ad1@adco.net", "fields":"cc"},
10 | {"destFolder": "Ads","matchingText": "newsletter@spam.com", "fields":"from"},
11 | {"destFolder": "Ads","matchingText": "pmp@training.net", "fields":"from"},
12 | {"destFolder": "Ads","matchingText": "joe.blow@blargh.net", "fields":"from"},
13 | {"type": "delete", "matchingText":"joe.blow@blargh.net", "fields":"from"},
14 | {"type": "delete", "olderThan" : 365}
15 | ]
16 | }
17 | ]
18 |
--------------------------------------------------------------------------------
/Chapter07/gui/src/main/java/com/steeplesoft/mailfilter/gui/MailFilter.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.gui;
2 |
3 | import javafx.application.Application;
4 | import static javafx.application.Application.launch;
5 | import javafx.fxml.FXMLLoader;
6 | import javafx.scene.Parent;
7 | import javafx.scene.Scene;
8 | import javafx.stage.Stage;
9 |
10 |
11 | public class MailFilter extends Application {
12 | @Override
13 | public void start(Stage stage) throws Exception {
14 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/mailfilter.fxml"));
15 |
16 | Scene scene = new Scene(root);
17 | scene.getStylesheets().add("/styles/Styles.css");
18 |
19 | stage.setTitle("MailFilter");
20 | stage.setScene(scene);
21 | stage.show();
22 | }
23 |
24 | public static void main(String[] args) {
25 | launch(args);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Chapter07/gui/src/main/java/com/steeplesoft/mailfilter/gui/RuleDescriptionFactory.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.gui;
2 |
3 | import com.steeplesoft.mailfilter.model.Rule;
4 | import javafx.beans.property.SimpleStringProperty;
5 | import javafx.beans.value.ObservableValue;
6 | import javafx.scene.control.TableColumn;
7 | import javafx.util.Callback;
8 |
9 | /**
10 | *
11 | * @author jason
12 | */
13 | public class RuleDescriptionFactory implements Callback, ObservableValue> {
14 |
15 | @Override
16 | public ObservableValue call(TableColumn.CellDataFeatures param) {
17 | String desc = "";
18 | final Rule rule = param.getValue();
19 | final String matchingText = rule.getMatchingText();
20 | switch (rule.getType()) {
21 | case MOVE:
22 | desc = (matchingText != null) ? String.format("Move emails matching '%s' to '%s'", matchingText, rule.getDestFolder()) : String.format("Move emails older than %s to '%s'", rule.getOlderThan(), rule.getDestFolder());
23 | break;
24 | case DELETE:
25 | desc = (matchingText != null) ? String.format("Delete emails matching '%s'", matchingText) : String.format("Delete emails older than %s", rule.getOlderThan());
26 | break;
27 | }
28 | return new SimpleStringProperty(desc);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Chapter07/gui/src/main/java/com/steeplesoft/mailfilter/service/MailFilterJob.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.service;
2 |
3 | import com.steeplesoft.mailfilter.MailFilter;
4 | import org.quartz.Job;
5 | import org.quartz.JobExecutionContext;
6 | import org.quartz.JobExecutionException;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class MailFilterJob implements Job {
13 | @Override
14 | public void execute(JobExecutionContext jec) throws JobExecutionException {
15 | MailFilter filter = new MailFilter();
16 | filter.run();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter07/gui/src/main/java/com/steeplesoft/mailfilter/service/MailFilterService.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.mailfilter.service;
2 |
3 | import java.util.logging.Level;
4 | import java.util.logging.Logger;
5 | import org.quartz.JobBuilder;
6 | import org.quartz.JobDetail;
7 | import org.quartz.Scheduler;
8 | import org.quartz.SchedulerException;
9 | import org.quartz.SimpleScheduleBuilder;
10 | import org.quartz.Trigger;
11 | import org.quartz.TriggerBuilder;
12 | import org.quartz.impl.StdSchedulerFactory;
13 |
14 | /**
15 | *
16 | * @author jason
17 | */
18 | public class MailFilterService {
19 | public static void main(String[] args) {
20 | try {
21 | final Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
22 | scheduler.start();
23 | final JobDetail job = JobBuilder.newJob(MailFilterJob.class).build();
24 | final Trigger trigger = TriggerBuilder.newTrigger()
25 | .startNow()
26 | .withSchedule(SimpleScheduleBuilder.simpleSchedule()
27 | .withIntervalInMinutes(15)
28 | .repeatForever())
29 | .build();
30 | scheduler.scheduleJob(job, trigger);
31 | } catch (SchedulerException ex) {
32 | Logger.getLogger(MailFilterService.class.getName()).log(Level.SEVERE, null, ex);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Chapter07/gui/src/main/resources/styles/Styles.css:
--------------------------------------------------------------------------------
1 | .button {
2 | -fx-font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/Chapter07/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | mailfilter-master
7 | 1.0-SNAPSHOT
8 | MailFilter - Master
9 | pom
10 |
11 |
12 | UTF-8
13 | 2.4.3
14 |
15 |
16 |
17 |
18 | org.testng
19 | testng
20 | 6.10
21 | test
22 |
23 |
24 |
25 |
26 | cli
27 | gui
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-compiler-plugin
35 | 3.6.1
36 |
37 | 9
38 | 9
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Chapter08/.gitignore:
--------------------------------------------------------------------------------
1 | /branding/target/
2 | /application/target/
3 | /photobeans-main/target/
4 | /photobeans-manager/target/
--------------------------------------------------------------------------------
/Chapter08/application/src/main/build/launcher.conf:
--------------------------------------------------------------------------------
1 | # ${HOME} will be replaced by user home directory according to platform
2 | default_userdir="${HOME}/.${APPNAME}/dev"
3 | default_mac_userdir="${HOME}/Library/Application Support/${APPNAME}/dev"
4 |
5 | # options used by the launcher by default, can be overridden by explicit
6 | # command line switches
7 | default_options="--branding photobeans -J-Xms24m -J-Xmx768m"
8 | # for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea
9 |
10 | # default location of JDK/JRE, can be overridden by using --jdkhome switch
11 | #jdkhome="/path/to/jdk"
12 |
13 | # clusters' paths separated by path.separator (semicolon on Windows, colon on Unices)
14 | #extra_clusters=
--------------------------------------------------------------------------------
/Chapter08/application/src/test/java/com/steeplesoft/photobeans/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans;
2 |
3 | import java.util.logging.Level;
4 | import junit.framework.Test;
5 | import org.netbeans.junit.NbModuleSuite;
6 | import org.netbeans.junit.NbTestCase;
7 |
8 | public class ApplicationTest extends NbTestCase {
9 |
10 | public static Test suite() {
11 | return NbModuleSuite.createConfiguration(ApplicationTest.class).
12 | gui(false).
13 | failOnMessage(Level.WARNING). // works at least in RELEASE71
14 | failOnException(Level.INFO).
15 | enableClasspathModules(false).
16 | clusters(".*").
17 | suite(); // RELEASE71+, else use NbModuleSuite.create(NbModuleSuite.createConfiguration(...))
18 | }
19 |
20 | public ApplicationTest(String n) {
21 | super(n);
22 | }
23 |
24 | public void testApplication() {
25 | // pass if there are merely no warnings/exceptions
26 | /* Example of using Jelly Tools (additional test dependencies required) with gui(true):
27 | new ActionNoBlock("Help|About", null).performMenu();
28 | new NbDialogOperator("About").closeByButton();
29 | */
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/Chapter08/branding/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.steeplesoft
7 | photobeans-parent
8 | 1.0-SNAPSHOT
9 |
10 |
11 | photobeans-branding
12 | nbm
13 |
14 | photobeans-branding
15 |
16 |
17 |
18 | org.netbeans.api
19 | org-netbeans-api-annotations-common
20 | ${netbeans.version}
21 |
22 |
23 |
24 |
25 |
26 |
27 | org.codehaus.mojo
28 | nbm-maven-plugin
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-jar-plugin
33 |
34 |
35 |
36 | ${project.build.outputDirectory}/META-INF/MANIFEST.MF
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/Bundle.properties:
--------------------------------------------------------------------------------
1 | currentVersion=PhotoBeans
2 | LBL_splash_window_title=Starting PhotoBeans
3 | SPLASH_HEIGHT=512
4 | SPLASH_WIDTH=512
5 | SplashProgressBarBounds=118,390,278,4
6 | SplashRunningTextBounds=118,372,278,12
7 | SplashRunningTextFontSize=18
8 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame.gif
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame32.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame32.gif
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame48.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame48.gif
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/splash.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/splash.gif
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties:
--------------------------------------------------------------------------------
1 | CTL_MainWindow_Title=PhotoBeans {0}
2 | CTL_MainWindow_Title_No_Project=PhotoBeans {0}
3 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle_en_US.properties:
--------------------------------------------------------------------------------
1 | CTL_MainWindow_Title=PhotoBeans
2 | CTL_MainWindow_Title_No_Project=PhotoBeans
3 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm-branding/modules/org-netbeans-core.jar/org/netbeans/core/ui/Bundle.properties:
--------------------------------------------------------------------------------
1 | LBL_ProductInformation=PhotoBeans
2 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/nbm/manifest.mf:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | OpenIDE-Module-Localizing-Bundle: com/steeplesoft/photobeans/branding/Bundle.properties
3 | AutoUpdate-Essential-Module: true
4 |
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/resources/camera-icon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/resources/camera-icon-16x16.png
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/resources/camera-icon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/resources/camera-icon-32x32.png
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/resources/camera-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter08/branding/src/main/resources/camera-icon-48x48.png
--------------------------------------------------------------------------------
/Chapter08/branding/src/main/resources/com/steeplesoft/photobeans/branding/Bundle.properties:
--------------------------------------------------------------------------------
1 | # Localized module labels. Defaults taken from POM (, , ) if unset.
2 | #OpenIDE-Module-Name=
3 | #OpenIDE-Module-Short-Description=
4 | #OpenIDE-Module-Long-Description=
5 | #OpenIDE-Module-Display-Category=
6 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/PhotoListTopComponent.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/PhotoViewerController.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main;
2 |
3 | import java.io.File;
4 | import java.net.URL;
5 | import java.util.ResourceBundle;
6 | import javafx.event.ActionEvent;
7 | import javafx.fxml.FXML;
8 | import javafx.fxml.Initializable;
9 | import javafx.scene.Group;
10 | import javafx.scene.control.ScrollPane;
11 | import javafx.scene.image.Image;
12 | import javafx.scene.image.ImageView;
13 | import javafx.scene.layout.BorderPane;
14 | import javafx.scene.layout.Pane;
15 | import org.openide.util.Utilities;
16 |
17 | /**
18 | *
19 | * @author jason
20 | */
21 | public class PhotoViewerController implements Initializable {
22 |
23 | @FXML
24 | private BorderPane borderPane;
25 | @FXML
26 | private ScrollPane scrollPane;
27 | @FXML
28 | private ImageView imageView;
29 | private Pane pane;
30 | private String photo;
31 | private Image image;
32 |
33 | @Override
34 | public void initialize(URL location, ResourceBundle resources) {
35 | // Group grp = new Group();
36 | // imageView = new ImageView();
37 | // imageView.setPreserveRatio(true);
38 | // scrollPane.setContent(grp);
39 | // grp.getChildren().add(imageView);
40 |
41 | imageView.fitWidthProperty().bind(borderPane.widthProperty());
42 | imageView.fitHeightProperty().bind(borderPane.heightProperty());
43 | imageView.setPreserveRatio(true);
44 | }
45 |
46 | public String getPhoto() {
47 | return photo;
48 | }
49 |
50 | public void setPhoto(String photo) {
51 | this.photo = photo;
52 | image = new Image(Utilities.toURI(new File(photo)).toString());
53 | imageView.setImage(image);
54 | }
55 |
56 | @FXML
57 | public void rotateLeft(ActionEvent event) {
58 | imageView.setRotate(imageView.getRotate() - 90);
59 | }
60 |
61 | @FXML
62 | public void rotateRight(ActionEvent event) {
63 | imageView.setRotate(imageView.getRotate() + 90);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/PhotoViewerTopComponent.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/factories/MonthNodeFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.photobeans.main.factories;
7 |
8 | import com.steeplesoft.photobeans.main.nodes.MonthNode;
9 | import com.steeplesoft.photobeans.manager.PhotoManager;
10 | import java.util.List;
11 | import java.util.logging.Level;
12 | import java.util.logging.Logger;
13 | import org.openide.LifecycleManager;
14 | import org.openide.nodes.ChildFactory;
15 | import org.openide.nodes.Node;
16 | import org.openide.util.Lookup;
17 |
18 | /**
19 | *
20 | * @author jason
21 | */
22 | public class MonthNodeFactory extends ChildFactory {
23 |
24 | private final PhotoManager photoManager;
25 | private static final Logger LOGGER = Logger.getLogger(YearChildFactory.class.getName());
26 | private final int year;
27 | public MonthNodeFactory(int year) {
28 | this.year = year;
29 | this.photoManager = Lookup.getDefault().lookup(PhotoManager.class);
30 | if (photoManager == null) {
31 | LOGGER.log(Level.SEVERE, "Cannot get PhotoManager object");
32 | LifecycleManager.getDefault().exit();
33 | }
34 | }
35 |
36 | @Override
37 | protected boolean createKeys(List toPopulate) {
38 | toPopulate.addAll(photoManager.getMonths(year));
39 | return true;
40 | }
41 |
42 | @Override
43 | protected Node createNodeForKey(String key) {
44 | return new MonthNode(year, Integer.parseInt(key));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/factories/PhotoNodeFactory.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.factories;
2 |
3 | import com.steeplesoft.photobeans.main.nodes.PhotoNode;
4 | import com.steeplesoft.photobeans.manager.PhotoManager;
5 | import java.util.List;
6 | import java.util.logging.Level;
7 | import java.util.logging.Logger;
8 | import org.openide.LifecycleManager;
9 | import org.openide.nodes.ChildFactory;
10 | import org.openide.nodes.Node;
11 | import org.openide.util.Lookup;
12 |
13 | /**
14 | *
15 | * @author jason
16 | */
17 | public class PhotoNodeFactory extends ChildFactory {
18 |
19 | private final PhotoManager photoManager;
20 | private static final Logger LOGGER = Logger.getLogger(YearChildFactory.class.getName());
21 | private final int year;
22 | private final int month;
23 |
24 | public PhotoNodeFactory(int year, int month) {
25 | this.year = year;
26 | this.month = month;
27 | this.photoManager = Lookup.getDefault().lookup(PhotoManager.class);
28 | if (photoManager == null) {
29 | LOGGER.log(Level.SEVERE, "Cannot get PhotoManager object");
30 | LifecycleManager.getDefault().exit();
31 | }
32 | }
33 |
34 | @Override
35 | protected boolean createKeys(List toPopulate) {
36 | toPopulate.addAll(photoManager.getPhotos(year, month));
37 | return true;
38 | }
39 |
40 | @Override
41 | protected Node createNodeForKey(String key) {
42 | return new PhotoNode(key);
43 | }
44 | }
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/factories/YearChildFactory.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.factories;
2 |
3 | import com.steeplesoft.photobeans.main.nodes.YearNode;
4 | import com.steeplesoft.photobeans.manager.PhotoManager;
5 | import java.util.List;
6 | import java.util.logging.Level;
7 | import java.util.logging.Logger;
8 | import org.openide.LifecycleManager;
9 | import org.openide.nodes.ChildFactory;
10 | import org.openide.nodes.Node;
11 | import org.openide.util.Lookup;
12 |
13 | /**
14 | *
15 | * @author jason
16 | */
17 | public class YearChildFactory extends ChildFactory {
18 |
19 | private final PhotoManager photoManager;
20 | private static final Logger LOGGER = Logger.getLogger(YearChildFactory.class.getName());
21 |
22 | public YearChildFactory() {
23 | this.photoManager = Lookup.getDefault().lookup(PhotoManager.class);
24 | if (photoManager == null) {
25 | LOGGER.log(Level.SEVERE, "Cannot get PhotoManager object");
26 | LifecycleManager.getDefault().exit();
27 | }
28 | }
29 |
30 | @Override
31 | protected boolean createKeys(List list) {
32 | list.addAll(photoManager.getYears());
33 | return true;
34 | }
35 |
36 | @Override
37 | protected Node createNodeForKey(String key) {
38 | return new YearNode(Integer.parseInt(key));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/nodes/MonthNode.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.nodes;
2 |
3 | import com.steeplesoft.photobeans.main.factories.PhotoNodeFactory;
4 | import java.time.Month;
5 | import java.time.format.TextStyle;
6 | import java.util.Locale;
7 | import org.openide.nodes.AbstractNode;
8 | import org.openide.nodes.Children;
9 | import org.openide.util.lookup.Lookups;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | public class MonthNode extends AbstractNode {
16 | public MonthNode(int year, int month) {
17 | super(Children.create(new PhotoNodeFactory(year, month), true), Lookups.singleton(month));
18 | String display = month + " - " + Month.values()[month-1].getDisplayName(TextStyle.FULL, Locale.getDefault());
19 | setName(display);
20 | setDisplayName(display);
21 | }
22 | }
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/nodes/PhotoNode.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.nodes;
2 |
3 | import java.io.File;
4 | import java.util.Set;
5 | import org.openide.cookies.OpenCookie;
6 | import org.openide.nodes.AbstractNode;
7 | import org.openide.nodes.Children;
8 | import org.openide.util.lookup.InstanceContent;
9 | import org.openide.windows.TopComponent;
10 | import org.openide.windows.WindowManager;
11 | import com.steeplesoft.photobeans.main.PhotoViewerTopComponent;
12 | import javax.swing.Action;
13 | import org.openide.actions.OpenAction;
14 | import org.openide.util.actions.SystemAction;
15 | import org.openide.util.lookup.AbstractLookup;
16 |
17 | /**
18 | *
19 | * @author jason
20 | */
21 | public class PhotoNode extends AbstractNode {
22 |
23 | public PhotoNode(String photo) {
24 | this(photo, new InstanceContent());
25 | }
26 |
27 | private PhotoNode(String photo, InstanceContent ic) {
28 | super(Children.LEAF, new AbstractLookup(ic));
29 | final String name = new File(photo).getName();
30 | setName(name);
31 | setDisplayName(name);
32 | setShortDescription(photo);
33 |
34 | ic.add((OpenCookie) () -> {
35 | TopComponent tc = findTopComponent(photo);
36 | if (tc == null) {
37 | tc = new PhotoViewerTopComponent(photo);
38 | tc.open();
39 | }
40 | tc.requestActive();
41 | });
42 | }
43 |
44 | private TopComponent findTopComponent(String photo) {
45 | Set openTopComponents = WindowManager.getDefault().getRegistry().getOpened();
46 | for (TopComponent tc : openTopComponents) {
47 | if (photo.equals(tc.getLookup().lookup(String.class))) {
48 | return tc;
49 | }
50 | }
51 | return null;
52 | }
53 |
54 | @Override
55 | public Action[] getActions(boolean context) {
56 | return new Action[]{SystemAction.get(OpenAction.class)};
57 | }
58 |
59 | @Override
60 | public Action getPreferredAction() {
61 | return SystemAction.get(OpenAction.class);
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/nodes/RootNode.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.nodes;
2 |
3 | import com.steeplesoft.photobeans.manager.reload.ReloadImagesAction;
4 | import com.steeplesoft.photobeans.main.factories.YearChildFactory;
5 | import com.steeplesoft.photobeans.manager.PhotoManager;
6 | import com.steeplesoft.photobeans.manager.reload.ReloadCookie;
7 | import javax.swing.Action;
8 | import org.openide.nodes.AbstractNode;
9 | import org.openide.nodes.Children;
10 | import org.openide.util.Lookup;
11 | import org.openide.util.LookupEvent;
12 | import org.openide.util.LookupListener;
13 | import org.openide.util.NbBundle;
14 | import org.openide.util.lookup.AbstractLookup;
15 | import org.openide.util.lookup.InstanceContent;
16 |
17 | /**
18 | *
19 | * @author jason
20 | */
21 | @NbBundle.Messages({
22 | "HINT_RootNode=Show all years",
23 | "LBL_RootNode=Photos"
24 | })
25 | public class RootNode extends AbstractNode {
26 |
27 | private final InstanceContent instanceContent;
28 | private Lookup.Result reloadResult = null;
29 |
30 | public RootNode() {
31 | this(new InstanceContent());
32 | }
33 |
34 | protected RootNode(InstanceContent ic) {
35 | super(Children.create(new YearChildFactory(), true),
36 | new AbstractLookup(ic));
37 | PhotoManager photoManager = Lookup.getDefault().lookup(PhotoManager.class);
38 | reloadResult = photoManager.getLookup().lookup(new Lookup.Template(ReloadCookie.class));
39 | reloadResult.addLookupListener(event -> setChildren(Children.create(new YearChildFactory(), true)));
40 |
41 | setDisplayName(Bundle.LBL_RootNode());
42 | setShortDescription(Bundle.HINT_RootNode());
43 |
44 | instanceContent = ic;
45 | }
46 |
47 | @Override
48 | public Action[] getActions(boolean context) {
49 | return new Action[]{new ReloadImagesAction(getLookup())};
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/nodes/YearNode.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.main.nodes;
2 |
3 | import com.steeplesoft.photobeans.main.factories.MonthNodeFactory;
4 | import org.openide.nodes.AbstractNode;
5 | import org.openide.nodes.Children;
6 | import org.openide.util.lookup.Lookups;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class YearNode extends AbstractNode {
13 | public YearNode(int year) {
14 | super(Children.create(new MonthNodeFactory(year), true), Lookups.singleton(year));
15 | setName("" + year);
16 | setDisplayName("" + year);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/main/options/package-info.java:
--------------------------------------------------------------------------------
1 | @OptionsPanelController.ContainerRegistration(id = "Photobeans",
2 | categoryName = "#OptionsCategory_Name_Photobeans",
3 | iconBase = "camera-icon-32x32.png",
4 | keywords = "#OptionsCategory_Keywords_Photobeans",
5 | keywordsCategory = "Photobeans")
6 | @NbBundle.Messages(value = {"OptionsCategory_Name_Photobeans=Photobeans",
7 | "OptionsCategory_Keywords_Photobeans=photobeans"})
8 | package com.steeplesoft.photobeans.main.options;
9 |
10 | import org.netbeans.spi.options.OptionsPanelController;
11 | import org.openide.util.NbBundle;
12 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/manager/PhotoManager.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.manager;
2 |
3 | import java.util.List;
4 | import org.openide.util.Lookup;
5 |
6 | /**
7 | *
8 | * @author jason
9 | */
10 | public interface PhotoManager extends Lookup.Provider {
11 | void scanSourceDirs();
12 | List getYears();
13 | List getMonths(int year);
14 | List getPhotos(int year, int month);
15 | }
16 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/manager/impl/Photo.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.manager.impl;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public class Photo {
8 | private final String sourceDir;
9 | private final int year;
10 | private final int month;
11 | private final String image;
12 |
13 | public Photo(String sourceDir, int year, int month, String image) {
14 | this.sourceDir = sourceDir;
15 | this.year = year;
16 | this.month = month;
17 | this.image = image;
18 | }
19 |
20 | public String getSourceDir() {
21 | return sourceDir;
22 | }
23 |
24 | public int getYear() {
25 | return year;
26 | }
27 |
28 | public int getMonth() {
29 | return month;
30 | }
31 |
32 | public String getImage() {
33 | return image;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/manager/reload/ReloadCookie.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.manager.reload;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public class ReloadCookie {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/java/com/steeplesoft/photobeans/manager/reload/ReloadImagesAction.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.photobeans.manager.reload;
2 |
3 | import com.steeplesoft.photobeans.manager.PhotoManager;
4 | import java.awt.event.ActionEvent;
5 | import javax.swing.AbstractAction;
6 | import org.openide.awt.ActionID;
7 | import org.openide.awt.ActionReference;
8 | import org.openide.awt.ActionRegistration;
9 | import org.openide.util.Lookup;
10 | import org.openide.util.NbBundle.Messages;
11 | import org.openide.util.RequestProcessor;
12 |
13 | @ActionID(
14 | category = "File",
15 | id = "com.steeplesoft.photobeans.main.ReloadImagesAction"
16 | )
17 | @ActionRegistration(
18 | displayName = "#CTL_ReloadImagesAction",
19 | lazy = false
20 | )
21 | @ActionReference(path = "Menu/File", position = 1300)
22 | @Messages("CTL_ReloadImagesAction=Reload")
23 | public final class ReloadImagesAction extends AbstractAction {
24 |
25 | public ReloadImagesAction() {
26 | putValue(AbstractAction.NAME, "Reload");
27 | }
28 |
29 | public ReloadImagesAction(Lookup lookup) {
30 | this();
31 | }
32 |
33 | @Override
34 | public boolean isEnabled() {
35 | return true;
36 | }
37 |
38 | @Override
39 | public void actionPerformed(ActionEvent e) {
40 | RequestProcessor.getDefault().execute(() ->
41 | Lookup.getDefault().lookup(PhotoManager.class).scanSourceDirs());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/nbm/manifest.mf:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | OpenIDE-Module-Localizing-Bundle: com/steeplesoft/photobeans/main/Bundle.properties
3 | OpenIDE-Module-Requires: org.openide.windows.WindowManager
4 |
5 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/resources/com/steeplesoft/photobeans/main/Bundle.properties:
--------------------------------------------------------------------------------
1 | #Localized module labels. Defaults taken from POM (, , ) if unset.
2 | #OpenIDE-Module-Name=
3 | #OpenIDE-Module-Short-Description=
4 | #OpenIDE-Module-Long-Description=
5 | #OpenIDE-Module-Display-Category=
6 | #Mon Feb 20 12:30:50 CST 2017
7 | PhotobeansPanel.jLabel1.text=jLabel1
8 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/resources/com/steeplesoft/photobeans/main/options/Bundle.properties:
--------------------------------------------------------------------------------
1 |
2 | PhotoBeansPanel.jPanel1.border.title=Sources
3 | PhotoBeansPanel.jTextField1.text=jTextField1
4 | PhotoBeansPanel.jLabel1.text=jLabel1
5 | PhotobeansPanel.jLabel1.text=Source Directories
6 | PhotobeansPanel.buttonAdd.text=Add
7 | PhotobeansPanel.buttonRemove.text=Remove
8 | SourceDirectoriesPanel.jLabel1.text=Source Directories
9 | SourceDirectoriesPanel.buttonRemove.text=Remove
10 | SourceDirectoriesPanel.buttonRemove1.text=Remove
11 | SourceDirectoriesPanel.buttonAdd.text=Add
12 |
--------------------------------------------------------------------------------
/Chapter08/photobeans-main/src/main/resources/fxml/photoviewer.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Chapter09/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
25 | spring-boot
26 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/model/package-info.java:
--------------------------------------------------------------------------------
1 | @XmlJavaTypeAdapters({
2 | @XmlJavaTypeAdapter(type = LocalDateTime.class,
3 | value = LocalDateTimeAdapter.class)
4 | })
5 | package com.steeplesoft.monumentum.model;
6 |
7 | import com.steeplesoft.monumentum.rest.LocalDateTimeAdapter;
8 | import java.time.LocalDateTime;
9 | import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
10 | import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
11 |
12 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/mongo/Collection.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.monumentum.mongo;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import javax.enterprise.util.Nonbinding;
8 | import javax.inject.Qualifier;
9 |
10 | /**
11 | *
12 | * @author jason
13 | */
14 | @Qualifier
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Target({ElementType.METHOD, ElementType.FIELD,
17 | ElementType.PARAMETER, ElementType.TYPE})
18 | public @interface Collection {
19 | @Nonbinding String value() default "unknown";
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/mongo/Producers.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.monumentum.mongo;
2 |
3 | import com.mongodb.MongoClient;
4 | import com.mongodb.client.MongoCollection;
5 | import com.mongodb.client.MongoDatabase;
6 | import javax.enterprise.context.RequestScoped;
7 | import javax.enterprise.inject.Produces;
8 | import javax.enterprise.inject.spi.InjectionPoint;
9 | import org.bson.Document;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | @RequestScoped
16 | public class Producers {
17 |
18 | private MongoClient mongoClient;
19 | private MongoDatabase database;
20 |
21 | private MongoClient getMongoClient() {
22 | if (mongoClient == null) {
23 | String host = System.getProperty("mongo.host", "localhost");
24 | String port = System.getProperty("mongo.port", "27017");
25 | mongoClient = new MongoClient(host, Integer.parseInt(port));
26 | }
27 | return mongoClient;
28 | }
29 |
30 | @Produces
31 | private MongoDatabase getDatabase() {
32 | if (database == null) {
33 | database = getMongoClient().getDatabase("monumentum");
34 | }
35 |
36 | return database;
37 | }
38 |
39 | @Produces
40 | @Collection
41 | public MongoCollection getCollection(InjectionPoint injectionPoint) {
42 | Collection mc = injectionPoint.getAnnotated().getAnnotation(Collection.class);
43 | return getDatabase().getCollection(mc.value());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/rest/LocalDateTimeAdapter.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.monumentum.rest;
2 |
3 | import java.time.LocalDateTime;
4 | import java.time.format.DateTimeFormatter;
5 | import java.util.regex.Pattern;
6 | import javax.xml.bind.annotation.adapters.XmlAdapter;
7 |
8 | /**
9 | *
10 | * @author jason
11 | */
12 | public class LocalDateTimeAdapter extends XmlAdapter {
13 | private static final Pattern JS_DATE = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+Z");
14 | private static final DateTimeFormatter DEFAULT_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
15 | private static final DateTimeFormatter JS_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
16 |
17 | @Override
18 | public LocalDateTime unmarshal(String date) {
19 | if (date == null) {
20 | return null;
21 | }
22 | return LocalDateTime.parse(date,
23 | (JS_DATE.matcher(date).matches())
24 | ? JS_FORMAT : DEFAULT_FORMAT);
25 | }
26 |
27 | @Override
28 | public String marshal(LocalDateTime date) {
29 | return date != null ? DEFAULT_FORMAT.format(date) : null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/rest/Monumentum.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.monumentum.rest;
2 |
3 | import com.steeplesoft.monumentum.rest.resource.AuthenticationResource;
4 | import com.steeplesoft.monumentum.rest.resource.NoteResource;
5 | import com.steeplesoft.monumentum.security.SecureFilter;
6 | import java.util.HashSet;
7 | import java.util.Set;
8 | import javax.ws.rs.ApplicationPath;
9 | import org.glassfish.jersey.media.sse.SseFeature;
10 |
11 | /**
12 | *
13 | * @author jason
14 | */
15 | @ApplicationPath("/api")
16 | public class Monumentum extends javax.ws.rs.core.Application {
17 | @Override
18 | public Set> getClasses() {
19 | Set> s = new HashSet<>();
20 | s.add(NoteResource.class);
21 | s.add(AuthenticationResource.class);
22 | s.add(SecureFilter.class);
23 | return s;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/security/KeyGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.monumentum.security;
7 |
8 | import java.security.Key;
9 | import javax.crypto.spec.SecretKeySpec;
10 | import javax.enterprise.context.ApplicationScoped;
11 | import javax.inject.Singleton;
12 |
13 |
14 | /**
15 | *
16 | * @author jason
17 | */
18 | @Singleton
19 | public class KeyGenerator {
20 | private Key key;
21 |
22 | public Key getKey() {
23 | if (key == null) {
24 | String keyString = System.getProperty("signing.key", "replace for production");
25 | key = new SecretKeySpec(keyString.getBytes(), 0, keyString.getBytes().length, "DES");
26 | }
27 |
28 | return key;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/security/Secure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.monumentum.security;
7 |
8 | import java.lang.annotation.ElementType;
9 | import java.lang.annotation.Retention;
10 | import java.lang.annotation.RetentionPolicy;
11 | import java.lang.annotation.Target;
12 | import javax.ws.rs.NameBinding;
13 |
14 | /**
15 | *
16 | * @author jason
17 | */
18 | @NameBinding
19 | @Retention(RetentionPolicy.RUNTIME)
20 | @Target({ElementType.TYPE, ElementType.METHOD})
21 | public @interface Secure {
22 | }
23 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/security/SecureFilter.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.monumentum.security;
2 |
3 | import io.jsonwebtoken.Jwts;
4 | import java.io.IOException;
5 | import javax.annotation.Priority;
6 | import javax.inject.Inject;
7 | import javax.ws.rs.Priorities;
8 | import javax.ws.rs.container.ContainerRequestContext;
9 | import javax.ws.rs.container.ContainerRequestFilter;
10 | import javax.ws.rs.core.HttpHeaders;
11 | import javax.ws.rs.core.Response;
12 | import javax.ws.rs.ext.Provider;
13 |
14 | /**
15 | *
16 | * @author jason
17 | */
18 | @Provider
19 | @Secure
20 | @Priority(Priorities.AUTHENTICATION)
21 | public class SecureFilter implements ContainerRequestFilter {
22 | @Inject
23 | private KeyGenerator keyGenerator;
24 |
25 | @Override
26 | public void filter(ContainerRequestContext requestContext) throws IOException {
27 | try {
28 | String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
29 | String token = authorizationHeader.substring("Bearer".length()).trim();
30 | Jwts.parser().setSigningKey(keyGenerator.getKey()).parseClaimsJws(token);
31 | } catch (Exception e) {
32 | requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Chapter09/src/main/java/com/steeplesoft/monumentum/security/UserProducer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.steeplesoft.monumentum.security;
7 |
8 | import com.mongodb.BasicDBObject;
9 | import com.mongodb.client.MongoCollection;
10 | import com.mongodb.client.MongoDatabase;
11 | import com.steeplesoft.monumentum.model.User;
12 | import com.steeplesoft.monumentum.mongo.Collection;
13 | import io.jsonwebtoken.Claims;
14 | import io.jsonwebtoken.Jws;
15 | import io.jsonwebtoken.Jwts;
16 | import javax.enterprise.context.RequestScoped;
17 | import javax.enterprise.inject.Produces;
18 | import javax.inject.Inject;
19 | import javax.servlet.http.HttpServletRequest;
20 | import javax.ws.rs.HeaderParam;
21 | import javax.ws.rs.core.HttpHeaders;
22 | import org.bson.Document;
23 |
24 | /**
25 | *
26 | * @author jason
27 | */
28 | @RequestScoped
29 | public class UserProducer {
30 |
31 | @Inject
32 | private KeyGenerator keyGenerator;
33 | @Inject
34 | HttpServletRequest req;
35 | @Inject
36 | @Collection("users")
37 | private MongoCollection users;
38 |
39 | @Produces
40 | public User getUser() {
41 | String authHeader = req.getHeader(HttpHeaders.AUTHORIZATION);
42 | if (authHeader != null && authHeader.contains("Bearer")) {
43 | String token = authHeader.substring("Bearer".length()).trim();
44 | Jws parseClaimsJws = Jwts.parser().setSigningKey(keyGenerator.getKey()).parseClaimsJws(token);
45 | return getUser(parseClaimsJws.getBody().getSubject());
46 | } else {
47 | return null;
48 | }
49 | }
50 |
51 | private User getUser(String email) {
52 | Document doc = users.find(new BasicDBObject("email", email)).first();
53 | if (doc != null) {
54 | return new User(doc);
55 | } else {
56 | return null;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Chapter09/src/main/resources/logging.properties:
--------------------------------------------------------------------------------
1 | handlers=java.util.logging.ConsoleHandler
2 | java.util.logging.ConsoleHandler.formatter=com.sun.enterprise.server.logging.ODLLogFormatter
3 | java.util.logging.ConsoleHandler.level=FINER
4 |
5 | .level=INFO
6 | PayaraMicro.level=INFO
7 | org.glassfish.jersey.level=FINE
--------------------------------------------------------------------------------
/Chapter09/src/main/webapp/WEB-INF/beans.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter09/src/main/webapp/WEB-INF/beans.xml
--------------------------------------------------------------------------------
/Chapter09/src/main/webapp/images/plus-225x225.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter09/src/main/webapp/images/plus-225x225.png
--------------------------------------------------------------------------------
/Chapter09/src/main/webapp/images/x-225x225.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter09/src/main/webapp/images/x-225x225.png
--------------------------------------------------------------------------------
/Chapter09/src/main/webapp/loginsuccess.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Monumentum - Log in success
5 |
6 |
7 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/Chapter09/src/main/webapp/monumentum.css:
--------------------------------------------------------------------------------
1 | #app{
2 | display: grid;
3 | grid-template-columns: 25% auto;
4 | }
5 |
6 | .note-list {
7 | display: grid;
8 | grid-template-columns: auto 20px;
9 | padding-right: 10px;
10 | }
11 |
12 | .note-form {
13 | padding: 0px 10px 0px 10px;
14 | }
15 | div {
16 | /*border: 1px red solid;*/
17 | }
--------------------------------------------------------------------------------
/Chapter10/.gitignore:
--------------------------------------------------------------------------------
1 | /api/target/
2 | /target/
3 | /function/target/
4 | /function/nbproject/
--------------------------------------------------------------------------------
/Chapter10/api/src/main/java/com/steeplesoft/cloudnotice/api/Recipient.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.cloudnotice.api;
2 |
3 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
5 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
6 |
7 | @DynamoDBTable(tableName = CloudNoticeDAO.TABLE_NAME)
8 | public class Recipient {
9 | private String id;
10 | private String type = "SMS";
11 | private String address = "";
12 |
13 | public Recipient() {
14 | }
15 |
16 | public Recipient(String type, String address) {
17 | this.type = type;
18 | this.address = address;
19 | }
20 |
21 | @DynamoDBHashKey(attributeName = "_id")
22 | public String getId() {
23 | return id;
24 | }
25 |
26 | public void setId(String id) {
27 | this.id = id;
28 | }
29 |
30 | @DynamoDBAttribute(attributeName = "type")
31 | public String getType() {
32 | return type;
33 | }
34 |
35 | public void setType(String type) {
36 | this.type = type;
37 | }
38 |
39 | @DynamoDBAttribute(attributeName="address")
40 | public String getAddress() {
41 | return address;
42 | }
43 |
44 | public void setAddress(String address) {
45 | this.address = address;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Chapter10/api/src/test/java/com/steeplesoft/cloudnotice/api/CloudNoticeDaoTest.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.cloudnotice.api;
2 |
3 | import java.util.List;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | public class CloudNoticeDaoTest {
8 | private final CloudNoticeDAO dao = new CloudNoticeDAO(true);
9 |
10 | @Test
11 | public void getRecipients() {
12 | dao.getRecipients();
13 | }
14 |
15 | @Test
16 | public void addRecipient() {
17 | Recipient recip = new Recipient("SMS", "test@example.com");
18 | dao.saveRecipient(recip);
19 | List recipients = dao.getRecipients();
20 | Assert.assertEquals(1, recipients.size());
21 | }
22 |
23 | @Test
24 | public void deleteRecipient() {
25 | Recipient recip = new Recipient("SMS", "test2@example.com");
26 |
27 | dao.saveRecipient(recip);
28 | Assert.assertEquals(1, dao.getRecipients().size());
29 |
30 | dao.deleteRecipient(recip);
31 | Assert.assertEquals(0, dao.getRecipients().size());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Chapter10/function/src/main/java/com/steeplesoft/cloudnotice/function/SnsEventHandler.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.cloudnotice.function;
2 |
3 | import com.amazonaws.services.lambda.runtime.Context;
4 | import com.amazonaws.services.lambda.runtime.LambdaLogger;
5 | import com.amazonaws.services.lambda.runtime.RequestHandler;
6 | import com.amazonaws.services.lambda.runtime.events.SNSEvent;
7 | import com.steeplesoft.cloudnotice.api.CloudNoticeDAO;
8 | import com.steeplesoft.cloudnotice.api.SnsClient;
9 | import com.steeplesoft.cloudnotice.api.Recipient;
10 | import com.steeplesoft.cloudnotice.api.SesClient;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * http://docs.aws.amazon.com/lambda/latest/dg/with-sns.html
16 | *
17 | * @author jason
18 | */
19 | public class SnsEventHandler implements RequestHandler {
20 |
21 | @Override
22 | public Object handleRequest(SNSEvent request, Context context) {
23 | final LambdaLogger logger = context.getLogger();
24 | final String message = request.getRecords().get(0).getSNS().getMessage();
25 | logger.log("Handle message '" + message + "'");
26 |
27 | final List recipients = new CloudNoticeDAO(false)
28 | .getRecipients();
29 | final List emailAddresses = recipients.stream()
30 | .filter(r -> "email".equalsIgnoreCase(r.getType()))
31 | .map(r -> r.getAddress())
32 | .collect(Collectors.toList());
33 | final List phoneNumbers = recipients.stream()
34 | .filter(r -> "sms".equalsIgnoreCase(r.getType()))
35 | .map(r -> r.getAddress())
36 | .collect(Collectors.toList());
37 | final SesClient sesClient = new SesClient();
38 | final SnsClient snsClient = new SnsClient();
39 |
40 | sesClient.sendEmails(emailAddresses, "j9bp@steeplesoft.com",
41 | "Cloud Notification", message);
42 | snsClient.sendTextMessages(phoneNumbers, message);
43 | sesClient.shutdown();
44 | snsClient.shutdown();
45 |
46 | logger.log("Message handling complete.");
47 |
48 | return null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Chapter10/function/src/test/java/com/steeplesoft/cloudnotice/function/SnsEventHandlerTest.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.cloudnotice.function;
2 |
3 | import com.amazonaws.services.lambda.runtime.Context;
4 | import com.amazonaws.services.lambda.runtime.events.SNSEvent;
5 | import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNS;
6 | import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import org.junit.After;
10 | import org.junit.AfterClass;
11 | import org.junit.Before;
12 | import org.junit.BeforeClass;
13 |
14 | public class SnsEventHandlerTest {
15 |
16 | public SnsEventHandlerTest() {
17 | }
18 |
19 | @BeforeClass
20 | public static void setUpClass() {
21 | }
22 |
23 | @AfterClass
24 | public static void tearDownClass() {
25 | }
26 |
27 | @Before
28 | public void setUp() {
29 | }
30 |
31 | @After
32 | public void tearDown() {
33 | }
34 |
35 | // @Test
36 | public void testHandleRequest() {
37 | System.out.println("handleRequest");
38 |
39 | SNS sns = new SNS();
40 | sns.setMessage("test message");
41 |
42 | SNSRecord record = new SNSRecord();
43 | record.setSns(sns);
44 |
45 | SNSEvent request = new SNSEvent();
46 | List records = new ArrayList<>();
47 | records.add(record);
48 | request.setRecords(records);
49 |
50 | Context context = new TestContext();
51 | SnsEventHandler instance = new SnsEventHandler();
52 |
53 | instance.handleRequest(request, context);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/Chapter10/manager/src/main/java/com/steeplesoft/cloudnotice/manager/CloudNoticeManager.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.cloudnotice.manager;
2 |
3 | import javafx.application.Application;
4 | import javafx.fxml.FXMLLoader;
5 | import javafx.scene.Parent;
6 | import javafx.scene.Scene;
7 | import javafx.stage.Stage;
8 |
9 | public class CloudNoticeManager extends Application {
10 | private FXMLLoader fxmlLoader;
11 |
12 | public static void main(String[] args) {
13 | launch(args);
14 | }
15 |
16 | @Override
17 | public void start(final Stage stage) throws Exception {
18 | fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/manager.fxml"));
19 | Parent root = fxmlLoader.load();
20 |
21 | Scene scene = new Scene(root);
22 | scene.getStylesheets().add("/styles/styles.css");
23 |
24 | stage.setTitle("CloudNotice");
25 | stage.setScene(scene);
26 | stage.show();
27 | }
28 |
29 | @Override
30 | public void stop() throws Exception {
31 | System.out.println("stop");
32 | CloudNoticeManagerController controller = (CloudNoticeManagerController) fxmlLoader.getController();
33 | controller.cleanup();
34 | super.stop();
35 | }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Chapter10/manager/src/main/resources/styles/styles.css:
--------------------------------------------------------------------------------
1 | .button {
2 | -fx-font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/Chapter11/.gitignore:
--------------------------------------------------------------------------------
1 | /deskdroid-desktop/target/
2 | /deskdroid-shared/target/
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | local.properties
4 | .idea/
5 | .DS_Store
6 | build
7 | captures
8 | .externalNativeBuild
9 | *.keystore
10 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\jason\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 | -dontwarn
28 | -ignorewarnings
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/AuthorizeClientActivity.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.net.wifi.WifiManager;
8 | import android.os.Bundle;
9 | import android.support.v4.content.LocalBroadcastManager;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.text.format.Formatter;
12 | import android.widget.TextView;
13 |
14 | import com.steeplesoft.deskdroid.service.DeskDroidService;
15 |
16 | import java.util.Random;
17 |
18 | public class AuthorizeClientActivity extends AppCompatActivity {
19 | private BroadcastReceiver messageReceiver;
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_authorize_client);
24 |
25 | WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
26 | String ipAddress = Formatter.formatIpAddress(wifiMgr.getConnectionInfo().getIpAddress());
27 |
28 | String code = Integer.toString(100000 + new Random().nextInt(900000));
29 |
30 | ((TextView) findViewById(R.id.ip)).setText(ipAddress);
31 | ((TextView) findViewById(R.id.code)).setText(code);
32 |
33 | messageReceiver = new BroadcastReceiver() {
34 | @Override
35 | public void onReceive(Context context, Intent intent) {
36 | clientAuthenticated();
37 | }
38 | };
39 | LocalBroadcastManager.getInstance(this).registerReceiver(
40 | messageReceiver, new IntentFilter(DeskDroidService.CODE_ACCEPTED));
41 |
42 | Intent intent = new Intent(DeskDroidService.CODE_GENERATED);
43 | intent.putExtra("code", code);
44 | LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
45 | }
46 |
47 | protected void clientAuthenticated() {
48 | LocalBroadcastManager.getInstance(this).unregisterReceiver(messageReceiver);
49 | setResult(2, new Intent());
50 | finish();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/BootReceiver.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.telephony.SmsMessage;
8 | import android.util.Log;
9 |
10 | import com.steeplesoft.deskdroid.service.DeskDroidService;
11 |
12 | public class BootReceiver extends BroadcastReceiver {
13 |
14 | @Override
15 | public void onReceive(Context context, Intent intent) {
16 | context.startService(new Intent(context, DeskDroidService.class));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/Permissions.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid;
2 |
3 | import android.Manifest;
4 |
5 | public enum Permissions {
6 | READ_SMS(Manifest.permission.READ_SMS, 1),
7 | SEND_SMS(Manifest.permission.SEND_SMS, 2),
8 | RECEIVE_SMS(Manifest.permission.RECEIVE_SMS, 3),
9 | RECEIVE_MMS(Manifest.permission.RECEIVE_MMS, 4),
10 | READ_CONTACTS(Manifest.permission.READ_CONTACTS, 5);
11 | final String permission;
12 | final int code;
13 |
14 | Permissions(String text, int code) {
15 | this.permission = text;
16 | this.code = code;
17 | }
18 | }
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/WifiReceiver.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid;
2 |
3 | import android.app.Service;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.net.wifi.WifiManager;
8 |
9 | import com.steeplesoft.deskdroid.service.DeskDroidService;
10 |
11 | public class WifiReceiver extends BroadcastReceiver {
12 |
13 | @Override
14 | public void onReceive(Context context, Intent intent) {
15 | WifiManager wifiMgr = (WifiManager) context.getApplicationContext()
16 | .getSystemService(Service.WIFI_SERVICE);
17 | if (wifiMgr.isWifiEnabled()) {
18 | context.startService(new Intent(context, DeskDroidService.class));
19 | } else {
20 | context.stopService(new Intent(context, DeskDroidService.class));
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/service/KeyGenerator.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.service;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import com.steeplesoft.deskdroid.R;
7 |
8 | import java.security.Key;
9 | import java.util.UUID;
10 |
11 | import javax.crypto.spec.SecretKeySpec;
12 |
13 | /**
14 | * Created by jason on 4/22/2017.
15 | */
16 |
17 | public class KeyGenerator {
18 | private static Key key;
19 | private static final Object lock = new Object();
20 |
21 | public static Key getKey(Context context) {
22 | synchronized (lock) {
23 | if (key == null) {
24 | SharedPreferences sharedPref = context.getSharedPreferences(
25 | context.getString(R.string.preference_deskdroid), Context.MODE_PRIVATE);
26 | String signingKey = sharedPref.getString(context.getString(R.string.preference_signing_key), null);
27 | if (signingKey == null) {
28 | signingKey = UUID.randomUUID().toString();
29 | final SharedPreferences.Editor edit = sharedPref.edit();
30 | edit.putString(context.getString(R.string.preference_signing_key), signingKey);
31 | edit.commit();
32 | }
33 | key = new SecretKeySpec(signingKey.getBytes(), 0, signingKey.getBytes().length, "DES");
34 | }
35 | }
36 |
37 | return key;
38 | }
39 | }
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/service/Secure.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.service;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | import javax.ws.rs.NameBinding;
9 |
10 | /**
11 | * Created by jason on 4/22/2017.
12 | */
13 |
14 | @NameBinding
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Target({ElementType.TYPE, ElementType.METHOD})
17 | public @interface Secure {
18 | }
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/java/com/steeplesoft/deskdroid/service/SecureFilter.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.service;
2 |
3 | import java.io.IOException;
4 | import java.security.Key;
5 |
6 | import javax.annotation.Priority;
7 | import javax.ws.rs.Priorities;
8 | import javax.ws.rs.container.ContainerRequestContext;
9 | import javax.ws.rs.container.ContainerRequestFilter;
10 | import javax.ws.rs.core.HttpHeaders;
11 | import javax.ws.rs.core.Response;
12 | import javax.ws.rs.ext.Provider;
13 |
14 | import io.jsonwebtoken.JwtParser;
15 | import io.jsonwebtoken.Jwts;
16 |
17 | /**
18 | * Created by jason on 4/27/2017.
19 | */
20 | @Provider
21 | @Secure
22 | @Priority(Priorities.AUTHENTICATION)
23 | public class SecureFilter implements ContainerRequestFilter {
24 | private DeskDroidService deskDroidService;
25 |
26 | public SecureFilter(DeskDroidService deskDroidService) {
27 | this.deskDroidService = deskDroidService;
28 | }
29 |
30 | @Override
31 | public void filter(ContainerRequestContext requestContext) throws IOException {
32 | try {
33 | String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
34 | String token = authorizationHeader.substring("Bearer".length()).trim();
35 | final Key key = KeyGenerator.getKey(deskDroidService.getApplicationContext());
36 | final JwtParser jwtParser = Jwts.parser().setSigningKey(key);
37 | jwtParser.parseClaimsJws(token);
38 | } catch (Exception e) {
39 | requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/layout/activity_authorize_client.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
27 |
28 |
35 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
29 |
30 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/green_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/green_check.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/red_x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-hdpi/red_x.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DeskDroid
3 | Settings
4 | signing.key
5 | deskdroid
6 |
7 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | mavenLocal()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:2.4.0-alpha7'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | mavenCentral()
21 | mavenLocal()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-desktop/src/main/java/com/steeplesoft/deskdroid/desktop/DeskDroidPreferences.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.desktop;
2 |
3 | import java.util.prefs.Preferences;
4 |
5 | /**
6 | *
7 | * @author jason
8 | */
9 | public class DeskDroidPreferences {
10 | private final Preferences prefs = Preferences.userRoot().node(this.getClass().getName());
11 |
12 | private static class Holder {
13 | private static final DeskDroidPreferences INSTANCE = new DeskDroidPreferences();
14 | }
15 |
16 | public static DeskDroidPreferences getInstance() {
17 | return Holder.INSTANCE;
18 | }
19 |
20 | private DeskDroidPreferences() {
21 |
22 | }
23 |
24 | public String getPhoneAddress() {
25 | return prefs.get("phoneAddress", "");
26 | }
27 |
28 | public void setPhoneAddress(String address) {
29 | prefs.put("phoneAddress", address);
30 | }
31 |
32 | public String getToken() {
33 | return prefs.get("token", null);
34 | }
35 |
36 | public void setToken(String token) {
37 | prefs.put("token", token);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-desktop/src/main/java/com/steeplesoft/deskdroid/desktop/MainApp.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.desktop;
2 |
3 | import javafx.application.Application;
4 | import static javafx.application.Application.launch;
5 | import javafx.fxml.FXMLLoader;
6 | import javafx.scene.Scene;
7 | import javafx.stage.Stage;
8 | import org.scenicview.ScenicView;
9 |
10 | public class MainApp extends Application {
11 | @Override
12 | public void start(Stage stage) throws Exception {
13 | Scene scene = new Scene(FXMLLoader.load(getClass().getResource("/fxml/deskdroid.fxml")));
14 | System.out.println(scene);
15 | scene.getStylesheets().add("/styles/deskdroid.css");
16 |
17 | stage.setTitle("DeskDroid");
18 | stage.setScene(scene);
19 | // ScenicView.show(scene);
20 | stage.show();
21 | }
22 |
23 | public static void main(String[] args) {
24 | launch(args);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-desktop/src/main/resources/fxml/connect.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-desktop/src/main/resources/styles/deskdroid.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-desktop/src/main/resources/styles/deskdroid.css
--------------------------------------------------------------------------------
/Chapter11/deskdroid-desktop/src/main/resources/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Java-9-Programming-Blueprints/bea9ee0825d31d991dc3382391cbe4a46d7f11ec/Chapter11/deskdroid-desktop/src/main/resources/unknown.png
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.steeplesoft
6 | deskdroid-master
7 | 1.0-SNAPSHOT
8 |
9 | deskdroid-shared
10 | DeskDroid - Shared
11 | jar
12 |
13 |
14 |
15 | org.apache.maven.plugins
16 | maven-compiler-plugin
17 | 3.6.1
18 |
19 |
20 | default-compile
21 |
22 |
23 | **/module-info.java
24 |
25 | 1.8
26 | 1.8
27 |
28 |
29 |
30 | module-infos
31 | compile
32 |
33 | compile
34 |
35 |
36 |
37 | **/*
38 |
39 |
40 | **/module-info.java
41 |
42 | 1.9
43 | 1.9
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/com/steeplesoft/deskdroid/model/Conversation.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.model;
2 |
3 | import java.util.List;
4 | import java.util.Set;
5 |
6 | /**
7 | *
8 | * @author jason
9 | */
10 | public class Conversation {
11 | private int threadId;
12 | private int messageCount;
13 | private String snippet;
14 | private List messages;
15 | private String participant;
16 |
17 | public int getThreadId() {
18 | return threadId;
19 | }
20 |
21 | public void setThreadId(int threadId) {
22 | this.threadId = threadId;
23 | }
24 |
25 | public int getMessageCount() {
26 | return messageCount;
27 | }
28 |
29 | public void setMessageCount(int messageCount) {
30 | this.messageCount = messageCount;
31 | }
32 |
33 | public String getSnippet() {
34 | return snippet;
35 | }
36 |
37 | public void setSnippet(String snippet) {
38 | this.snippet = snippet;
39 | }
40 |
41 | public List getMessages() {
42 | return messages;
43 | }
44 |
45 | public void setMessages(List messages) {
46 | this.messages = messages;
47 | }
48 |
49 | public String getParticipant() {
50 | return participant;
51 | }
52 |
53 | public void setParticipant(String participant) {
54 | this.participant = participant;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/com/steeplesoft/deskdroid/model/ConversationComparator.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.model;
2 |
3 | import java.util.Comparator;
4 | import java.util.List;
5 |
6 | /**
7 | *
8 | * @author jason
9 | */
10 | public class ConversationComparator implements Comparator {
11 |
12 | @Override
13 | public int compare(Conversation o1, Conversation o2) {
14 | if (o1 == null) {
15 | return 1;
16 | }
17 | if (o2 == null) {
18 | return -1;
19 | }
20 | List m1 = o1.getMessages();
21 | List m2 = o2.getMessages();
22 |
23 | if (m1 == null || m1.isEmpty()) {
24 | return 1;
25 | }
26 | if (m2 == null || m2.isEmpty()) {
27 | return -1;
28 | }
29 |
30 | Message last1 = m1.get(m1.size() - 1);
31 | Message last2 = m2.get(m2.size() - 1);
32 |
33 | return last2.getDate().compareTo(last1.getDate());
34 | }
35 | }
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/com/steeplesoft/deskdroid/model/Message.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.model;
2 |
3 | import java.util.Date;
4 |
5 | public class Message {
6 | private int id;
7 | private int threadId;
8 | private String address;
9 | private String body;
10 | private boolean mine;
11 | private Date date = new Date();
12 |
13 | public Message() {
14 | }
15 |
16 | public Message(String address, String body) {
17 | this.address = address;
18 | this.body = body;
19 | }
20 |
21 | public int getId() {
22 | return id;
23 | }
24 |
25 | public void setId(int id) {
26 | this.id = id;
27 | }
28 |
29 | public int getThreadId() {
30 | return threadId;
31 | }
32 |
33 | public void setThreadId(int threadId) {
34 | this.threadId = threadId;
35 | }
36 |
37 | public String getAddress() {
38 | return address;
39 | }
40 |
41 | public void setAddress(String address) {
42 | this.address = address;
43 | }
44 |
45 | public String getBody() {
46 | return body;
47 | }
48 |
49 | public void setBody(String body) {
50 | this.body = body;
51 | }
52 |
53 | public boolean isMine() {
54 | return mine;
55 | }
56 |
57 | public void setMine(boolean mine) {
58 | this.mine = mine;
59 | }
60 |
61 | public Date getDate() {
62 | return date;
63 | }
64 |
65 | public void setDate(Date date) {
66 | this.date = date;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/com/steeplesoft/deskdroid/model/MessageComparator.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.model;
2 |
3 | import java.util.Comparator;
4 |
5 | public class MessageComparator implements Comparator {
6 | @Override
7 | public int compare(Message message1, Message message2) {
8 | if (message2.getDate() == null) {
9 | return -1;
10 | }
11 | if (message1.getDate() == null) {
12 | return -1;
13 | }
14 | return message1.getDate().compareTo(message2.getDate());
15 | }
16 | }
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/com/steeplesoft/deskdroid/model/Participant.java:
--------------------------------------------------------------------------------
1 | package com.steeplesoft.deskdroid.model;
2 |
3 | /**
4 | *
5 | * @author jason
6 | */
7 | public class Participant {
8 | private String name;
9 | private String phoneNumber;
10 | private String thumbnail;
11 | private boolean hasThumbnail;
12 |
13 | public String getName() {
14 | return name;
15 | }
16 |
17 | public void setName(String name) {
18 | this.name = name;
19 | }
20 |
21 | public String getPhoneNumber() {
22 | return phoneNumber;
23 | }
24 |
25 | public void setPhoneNumber(String phoneNumber) {
26 | this.phoneNumber = phoneNumber;
27 | }
28 |
29 | public String getThumbnail() {
30 | return thumbnail;
31 | }
32 |
33 | public void setThumbnail(String thumbnail) {
34 | this.thumbnail = thumbnail;
35 | hasThumbnail = true;
36 | }
37 |
38 | public boolean hasThumbnail() {
39 | return hasThumbnail;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Chapter11/deskdroid-shared/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module deskdroid.shared {
2 | exports com.steeplesoft.deskdroid.model;
3 | }
--------------------------------------------------------------------------------
/Chapter11/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.steeplesoft
5 | deskdroid-master
6 | 1.0-SNAPSHOT
7 | DeskDroid - Master
8 | pom
9 |
10 |
11 | UTF-8
12 |
13 |
14 |
15 | deskdroid-desktop
16 | deskdroid-shared
17 |
18 |
19 |
20 |
21 | org.testng
22 | testng
23 | 6.10
24 | test
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------