groupNames, EventBus localEventBus){
47 | this.localEventBus = localEventBus;
48 | this.sessionId = sessionId;
49 | this.username = username;
50 | this.groupNames = groupNames;
51 | this.addListener(this);
52 | this.setRefreshInterval(REFRESH_INTERVAL);
53 | }
54 |
55 |
56 |
57 | public void start(){
58 | GlobalEventBus.addClient(username, sessionId, groupNames);
59 | refreshThread = new RefreshThread();
60 | refreshThread.start();
61 | }
62 |
63 |
64 | public void stop(){
65 | GlobalEventBus.removeClient(username, sessionId);
66 | refreshThread.interrupt();
67 |
68 | }
69 |
70 |
71 |
72 | @Override
73 | public void refresh(Refresher source) {
74 | if (receivedEvents!=null){
75 | for (com.mvplite.event.Event e : receivedEvents)
76 | localEventBus.fireEvent(e);
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | mvp-lite
6 | com.mvplite
7 | 2.0
8 | jar
9 | Module for work with media
10 |
11 |
12 | UTF-8
13 | 7.0.4
14 |
15 |
16 |
17 | org.vaadin.addons
18 | refresher
19 | 1.2.1.7
20 |
21 |
22 |
23 | com.vaadin
24 | vaadin-server
25 | ${vaadin.version}
26 |
27 |
28 | com.vaadin
29 | vaadin-client-compiled
30 | ${vaadin.version}
31 |
32 |
33 | com.vaadin
34 | vaadin-client
35 | ${vaadin.version}
36 | provided
37 |
38 |
39 | com.vaadin
40 | vaadin-themes
41 | ${vaadin.version}
42 |
43 |
44 |
45 | junit
46 | junit
47 | 4.10
48 | test
49 |
50 |
51 |
52 |
53 |
54 | vaadin-addons
55 | http://maven.vaadin.com/vaadin-addons
56 |
57 |
58 |
59 | src/main/test
60 |
61 |
62 |
63 | org.apache.maven.plugins
64 | maven-compiler-plugin
65 | 2.3.2
66 |
67 | 1.7
68 | 1.7
69 | UTF-8
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/main/webapp/VAADIN/themes/img/testDiv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Google Navigator
7 |
108 |
109 |
110 |
111 | Google example:
112 |
113 |
114 | Help home
115 | Upload or download files
116 | Number 3
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/target/surefire-reports/TEST-test.EventBusTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/webapp/VAADIN/themes/img/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Google Navigator
7 |
119 |
120 |
121 |
122 | Google example:
123 | http://support.google.com/docs/bin/answer.py?hl=en&answer=176692
124 |
125 |
133 |
134 |
--------------------------------------------------------------------------------
/src/main/java/com/mvplite/view/LiteNavigationController.java:
--------------------------------------------------------------------------------
1 | package com.mvplite.view;
2 |
3 | import java.io.Serializable;
4 | import java.util.LinkedHashMap;
5 | import java.util.LinkedHashSet;
6 | import java.util.LinkedList;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 | import com.mvplite.event.EventBus;
12 | import com.mvplite.event.Show404ViewEvent;
13 | import com.vaadin.server.Page;
14 |
15 | /**
16 | * The {@link LiteNavigationController} is the simplest {@link NavigationController}.
17 | * The {@link LiteNavigationController} supports Browser history (back and forward) by generating
18 | * uri fragments.
19 | * Note: the uri fragments does not contain any state and therefore bookmarks are
20 | * not supported by the {@link LiteNavigationController}.
21 | * In the future a BookmarkableNavigationController will be implemented to support bookmarks.
22 | * @author Hannes Dorfmann
23 | *
24 | */
25 | public class LiteNavigationController implements NavigationController, Page.UriFragmentChangedListener {
26 |
27 |
28 | private static final long serialVersionUID = 2585755661712329836L;
29 |
30 | private class HistoryEntry implements Serializable{
31 |
32 | private static final long serialVersionUID = 6852274305903891448L;
33 |
34 | public List eventsToFire;
35 |
36 | public HistoryEntry(){
37 | eventsToFire = new LinkedList<>();
38 | }
39 | }
40 |
41 | private EventBus eventBus;
42 | private final Map historyStack;
43 | private final Set listeners;
44 | private boolean fire404OnUnknownURI;
45 | private boolean setCurrentViewCausedByHistoryChange;
46 | private final Page page;
47 |
48 |
49 | public LiteNavigationController(){
50 | super();
51 | historyStack = new LinkedHashMap<>();
52 | listeners = new LinkedHashSet<>();
53 | setCurrentViewCausedByHistoryChange = false;
54 | this.page = Page.getCurrent();
55 | page.addUriFragmentChangedListener(this);
56 | // page.getUriFragment()
57 | setFire404OnUnknownUriFragment(false);
58 | }
59 |
60 | public LiteNavigationController(EventBus eventBus){
61 | this();
62 | this.eventBus = eventBus;
63 | }
64 |
65 |
66 |
67 | @Override
68 | public EventBus getEventBus(){
69 | return eventBus;
70 | }
71 |
72 | public void setEventBus(EventBus eb){
73 | this.eventBus = eb;
74 | }
75 |
76 | /* (non-Javadoc)
77 | * @see com.mvplite.view.NavigationController#setShowErrorMessageOnUnknownUriFragment(boolean)
78 | */
79 | @Override
80 | public void setFire404OnUnknownUriFragment(boolean showErrorMessage){
81 | this.fire404OnUnknownURI = showErrorMessage;
82 | }
83 |
84 |
85 |
86 | /* (non-Javadoc)
87 | * @see com.mvplite.view.NavigationController#addListener(com.mvplite.view.LiteNavigationController.NavigationControllerListener)
88 | */
89 | @Override
90 | public void addListener(NavigationControllerListener l){
91 | listeners.add(l);
92 | }
93 |
94 |
95 | /* (non-Javadoc)
96 | * @see com.mvplite.view.NavigationController#removeListener(com.mvplite.view.LiteNavigationController.NavigationControllerListener)
97 | */
98 | @Override
99 | public void removeListener(NavigationControllerListener l){
100 | listeners.remove(l);
101 | }
102 |
103 |
104 | private List calculateEventsToFireList(NavigateableView view){
105 | List events =
106 | new LinkedList();
107 |
108 | while (view != null)
109 | {
110 | events.add(0, view.getEventToShowThisView());
111 |
112 | if (view instanceof NavigateableSubView)
113 | view = ((NavigateableSubView) view).getParentView();
114 | else
115 | view = null;
116 | }
117 |
118 | return events;
119 | }
120 |
121 | public void clearUriFragments(){
122 | page.setUriFragment("", false);
123 | }
124 |
125 | private String calculateUri(NavigateableView view){
126 | String uri ="";
127 |
128 | while (view != null){
129 | uri = "/"+view.getUriFragment()+uri;
130 |
131 | if (view instanceof NavigateableSubView)
132 | view = ((NavigateableSubView) view).getParentView();
133 | else
134 | view = null;
135 | }
136 |
137 | return uri;
138 | }
139 |
140 | /* (non-Javadoc)
141 | * @see com.mvplite.view.NavigationController#setCurrentView(com.mvplite.view.NavigateableView)
142 | */
143 | @Override
144 | public void setCurrentView(NavigateableView view){
145 |
146 | if (!setCurrentViewCausedByHistoryChange){
147 | // Add url fragmentHistory support
148 | String uriFragment = calculateUri(view);
149 | HistoryEntry entry = new HistoryEntry();
150 | entry.eventsToFire = calculateEventsToFireList(view);
151 |
152 | if (historyStack.isEmpty())
153 | historyStack.put("", entry);
154 |
155 | historyStack.put(uriFragment, entry);
156 | page.setUriFragment(uriFragment, false); // Seems not to work at first call
157 | }
158 | fireNavigatedTo(view);
159 | }
160 |
161 | private void fireNavigatedTo(NavigateableView view){
162 | for (NavigationControllerListener l : listeners)
163 | l.onNavigatedTo(view);
164 | }
165 |
166 |
167 | /* (non-Javadoc)
168 | * @see com.mvplite.view.NavigationController#setStartView(com.mvplite.view.NavigateableView)
169 | */
170 | @Override
171 | public void setStartView(NavigateableView view){
172 | HistoryEntry entry = new HistoryEntry();
173 | entry.eventsToFire = calculateEventsToFireList(view);
174 |
175 | historyStack.put("", entry);
176 | }
177 |
178 |
179 |
180 | @Override
181 | public void uriFragmentChanged(Page.UriFragmentChangedEvent source) {
182 |
183 | // used by the back and forward browser button
184 |
185 | if (source == null)
186 | return;
187 |
188 | String uriFragment = source.getUriFragment();
189 |
190 | HistoryEntry entry = historyStack.get(uriFragment);
191 |
192 | if (entry == null){
193 |
194 | if (fire404OnUnknownURI){
195 | eventBus.fireEvent(new Show404ViewEvent());
196 | }
197 |
198 | }
199 | else
200 | {
201 | setCurrentViewCausedByHistoryChange = true;
202 |
203 | // fire the events that are needed to get to the state of uri fragment
204 | for (com.mvplite.event.Event e : entry.eventsToFire){
205 | eventBus.fireEvent(e);
206 | }
207 |
208 | setCurrentViewCausedByHistoryChange = false;
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/java/com/mvplite/event/GlobalEventBus.java:
--------------------------------------------------------------------------------
1 | package com.mvplite.event;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Date;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.TreeSet;
8 |
9 |
10 | public final class GlobalEventBus {
11 |
12 | /**
13 | * The Client represents a Client, since a user can have more than one clients (Browser-windows)
14 | * opened at the same time.
15 | * @author Hannes Dorfmann
16 | *
17 | */
18 | public final static class Client implements Comparable {
19 | public String sessionID;
20 | public long lastAccess;
21 | public List groupMemberships;
22 | public List queuedEvents;
23 |
24 | public Client(String sessionID, List groupMemberships){
25 | this.sessionID = sessionID;
26 | this.groupMemberships = groupMemberships;
27 | this.lastAccess = new Date().getTime();
28 | this.queuedEvents = new ArrayList();
29 | }
30 |
31 |
32 | @Override
33 | public int compareTo(Client o) {
34 | return this.sessionID.compareTo(o.sessionID);
35 | }
36 |
37 | @Override
38 | public String toString(){
39 | return queuedEvents.size()+ " "+ queuedEvents.toString()+" "+super.toString();
40 | }
41 |
42 | }
43 |
44 |
45 | private static long MEMORY_CLEAN_TIMEOUT = 5 * 60 * 1000;
46 | private static long CLIENT_TIMEOUT = 1 * 60 * 1000;
47 | private static long lastCleanUp = new Date().getTime();
48 |
49 |
50 | private static HashMap > userClientMap =
51 | new HashMap>();
52 |
53 | private static HashMap> groupClientMap =
54 | new HashMap>();
55 |
56 | public static void addClient(String username, String sessionID,
57 | List groupMemberships){
58 |
59 | Client c = new Client(sessionID, groupMemberships);
60 |
61 | TreeSet clientsOfUser = userClientMap.get(username);
62 |
63 | if (clientsOfUser == null) // No other Client of the same user is present
64 | { // TODO synchornized Thread Safe
65 | clientsOfUser = (new TreeSet());
66 | userClientMap.put(username, clientsOfUser);
67 | }
68 |
69 | clientsOfUser.add(c);
70 |
71 | if (groupMemberships!=null)
72 | for (String name : groupMemberships)
73 | {
74 | List group = groupClientMap.get(name);
75 | if (group == null){
76 | group = new ArrayList();
77 | groupClientMap.put(name, group);
78 | }
79 |
80 | group.add(c);
81 | }
82 |
83 | }
84 |
85 |
86 | public static void removeClient(String username, String sessionID){
87 |
88 | TreeSet clientsOfUser = userClientMap.get(username);
89 | Client toRemove = null;
90 |
91 | if (clientsOfUser != null){
92 | toRemove = null;
93 | for (Client c: clientsOfUser){
94 | if (c.sessionID.equals(sessionID))
95 | {
96 | toRemove = c;
97 | break;
98 | }
99 |
100 | }
101 |
102 | if (toRemove != null)
103 | clientsOfUser.remove(toRemove);
104 | }
105 |
106 |
107 | // remove group membershipments
108 | if (toRemove != null)
109 | for (String groupName : toRemove.groupMemberships)
110 | {
111 | List groups = groupClientMap.get(groupName);
112 |
113 | if (groups!=null)
114 | for (Client client : groups)
115 | if (client.sessionID.equals(sessionID))
116 | {
117 | toRemove = client;
118 | break;
119 | }
120 |
121 | groups.remove(toRemove);
122 | }
123 | }
124 |
125 |
126 |
127 |
128 | /**
129 | * Fire a event which is delivered to all clients of the user (identified by the username)
130 | * @param username
131 | * @param event
132 | */
133 | public static void fireEventToUser(String username, Event event){
134 |
135 | memoryCleanUp();
136 |
137 | TreeSet clients = userClientMap.get(username);
138 |
139 | if (clients != null) // If there is at least one client
140 | {
141 | for (Client c : clients)
142 | c.queuedEvents.add(event);
143 | }
144 |
145 | }
146 |
147 | /**
148 | * Fire a {@link Event} (broadcast) to every client, excepted the sender himself
149 | * (which is identified by the session Id)
150 | * @param event
151 | * @param sessionIdOfSender
152 | */
153 | public static void fireBroadcastEvent(Event event,
154 | String sessionIdOfSender){
155 |
156 | memoryCleanUp();
157 |
158 | for (TreeSet e : userClientMap.values())
159 | {
160 | for (Client c: e)
161 | if (c.sessionID.equals(sessionIdOfSender))
162 | continue;
163 | else
164 | c.queuedEvents.add(event);
165 | }
166 |
167 | }
168 |
169 | /**
170 | * Fire a {@link Event} (broadcast) to every registered Client.
171 | * The {@link Event} is also delivered to the sender.
172 | * @param event
173 | */
174 | public static void fireBroadcastEvent(Event event){
175 |
176 | memoryCleanUp();
177 |
178 | for (TreeSet e : userClientMap.values())
179 | {
180 | for (Client c: e)
181 | c.queuedEvents.add(event);
182 | }
183 |
184 | }
185 |
186 | /**
187 | * Fire the {@link Event} to all group-member-clients excluding the sender itself
188 | * @param e
189 | * @param sessionIdOfSender
190 | */
191 | public static void fireGroupBroadcastEvent(Event e,
192 | String groupName, String sessionIdOfSender){
193 |
194 | memoryCleanUp();
195 |
196 | List clients = groupClientMap.get(groupName);
197 |
198 | if (clients!=null)
199 | for (Client c: clients)
200 | if (sessionIdOfSender.equals(c.sessionID))
201 | continue;
202 | else
203 | c.queuedEvents.add(e);
204 | }
205 |
206 |
207 | /**
208 | * Fire a {@link Event} to every client,
209 | * that is member of a group (identified by the passed groupName)
210 | * @param e
211 | */
212 | public static void fireGroupBroadcastEvent(Event e,
213 | String groupName){
214 |
215 | memoryCleanUp();
216 |
217 | List clients = groupClientMap.get(groupName);
218 | if (clients!=null)
219 | for (Client c: clients)
220 | c.queuedEvents.add(e);
221 | }
222 |
223 | /**
224 | * Retrieve all queued {@link Event}s for a client.
225 | * This method is used by {@link GlobalEventBusDispatcher}.
226 | * @param username The username
227 | * @param sessionID the session id of the client
228 | * @return {@link List} of {@link Event}s or null
229 | */
230 | public static List getEventsFor(String username, String sessionID){
231 |
232 | TreeSet clients = userClientMap.get(username);
233 |
234 | if (clients != null)
235 | {
236 | for (Client c: clients)
237 | if (c.sessionID.equals(sessionID)){
238 | List ret =
239 | new ArrayList(c.queuedEvents);
240 |
241 | c.queuedEvents.clear();
242 | c.lastAccess = new Date().getTime();
243 | return ret;
244 | }
245 |
246 | }
247 |
248 |
249 | return null;
250 | }
251 |
252 |
253 | private static void memoryCleanUp(){
254 |
255 | long timeStamp = new Date().getTime();
256 |
257 | if (lastCleanUp + MEMORY_CLEAN_TIMEOUT < timeStamp){
258 |
259 | for (TreeSet clients : userClientMap.values()){
260 |
261 | List toRemove = new ArrayList();
262 |
263 | for (Client c: clients)
264 | if (c.lastAccess + CLIENT_TIMEOUT < timeStamp){
265 | toRemove.add(c);
266 | c.queuedEvents.clear();
267 |
268 | // Remove memberships
269 | for (String groupName : c.groupMemberships)
270 | {
271 | List s = groupClientMap.get(groupName);
272 | s.remove(c);
273 | }
274 | }
275 |
276 | if (!toRemove.isEmpty())
277 | {
278 | clients.removeAll(toRemove);
279 | }
280 | }
281 |
282 | lastCleanUp = new Date().getTime();
283 | }
284 |
285 | }
286 |
287 | }
288 |
--------------------------------------------------------------------------------
/src/main/java/com/mvplite/view/ui/Breadcrumbs.java:
--------------------------------------------------------------------------------
1 | package com.mvplite.view.ui;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 |
6 | import com.mvplite.view.NavigateableSubView;
7 | import com.mvplite.view.NavigateableView;
8 | import com.mvplite.view.NavigationController;
9 | import com.mvplite.view.NavigationControllerListener;
10 | import com.vaadin.ui.Button;
11 | import com.vaadin.ui.Button.ClickEvent;
12 | import com.vaadin.ui.Component;
13 | import com.vaadin.ui.CssLayout;
14 | import com.vaadin.ui.Label;
15 | import com.vaadin.ui.themes.BaseTheme;
16 |
17 | /**
18 | * This is a UI Component, that displays breadcrumbs for the
19 | * {@link NavigateableView} / {@link NavigateableSubView}, which is currently
20 | * displayed on screen.
21 | * To customize the look of this component, you should write your own
22 | * {@link SeparatorFactory} and {@link BreadcrumbElementFactory} and set it with
23 | * {@link #setBreadcrumbElementFactory(BreadcrumbElementFactory)}
24 | * {@link #setSeparatorFactory(SeparatorFactory)}
25 | *
26 | * @author Hannes Dorfmann
27 | */
28 | public class Breadcrumbs extends CssLayout implements NavigationControllerListener{
29 |
30 |
31 | private static final long serialVersionUID = -7308648462712616003L;
32 |
33 | public static String BREADCRUMB_ELEMENT = "breadcrumb-element";
34 |
35 | public static String BREADCRUMB_BAR = "breadcrumb-bar";
36 |
37 |
38 | /**
39 | * Factory interface for creating breadcrumb separators.
40 | *
41 | * @see Breadcrumbs#setSeparatorFactory(SeparatorFactory)
42 | * @author Petter Holmström
43 | * @since 1.0
44 | */
45 | public interface SeparatorFactory extends java.io.Serializable {
46 | /**
47 | * Creates and returns a component to be used as a separator between
48 | * breadcrumbs.
49 | */
50 | Component createSeparator();
51 | }
52 |
53 | /**
54 | * Default implementation of {@link SeparatorFactory}. The separators are
55 | * labels containing the "»" character and having the
56 | * {@link Breadcrumbs#BREADCRUMB_ELEMENT} style.
57 | *
58 | * @author Petter Holmström
59 | * @since 1.0
60 | */
61 | public class DefaultSeparatorFactory implements SeparatorFactory {
62 |
63 | private static final long serialVersionUID = 7957216244739746986L;
64 |
65 | @Override
66 | public Component createSeparator() {
67 | final Label separator = new Label("»");
68 | separator.setSizeUndefined();
69 | separator.addStyleName(BREADCRUMB_ELEMENT);
70 | return separator;
71 | }
72 | }
73 |
74 | /**
75 | * Factory interface for creating breadcrumb buttons.
76 | *
77 | * @see Breadcrumbs#setBreadcrumbElementFactory(BreadcrumbElementFactory)
78 | * @author Petter Holmström
79 | * @since 1.0
80 | */
81 | public interface BreadcrumbElementFactory extends java.io.Serializable {
82 |
83 | /**
84 | * Creates and returns a gui component for the specified view.
85 | * @param controller
86 | * @param view
87 | * @param currentIndex the index beginning by zero to totalcount-1
88 | * @param totalCount The total count of breadcrumb elements
89 | */
90 | public Component createElement(NavigationController controller, NavigateableView view, int currentIndex, int totalCount);
91 |
92 | /**
93 | * Updates the button texts. This method is called when the display name
94 | * and/or the description of the specified view are changed.
95 | *
96 | */
97 | @Deprecated
98 | void updateButtonTexts(Button button, NavigateableView view);
99 |
100 |
101 | public void setBreadcrumbElementStyleName(String styleName);
102 | }
103 |
104 |
105 |
106 | /**
107 | * Default implementation of {@link BreadcrumbElementFactory}. The created buttons have
108 | * the {@link BaseTheme#BUTTON_LINK} and
109 | * {@link Breadcrumbs#BREADCRUMB_ELEMENT} styles.
110 | *
111 | * @author Petter Holmström
112 | * @since 1.0
113 | */
114 | public class DefaultBreadcrumbElementFactory implements BreadcrumbElementFactory {
115 |
116 | private static final long serialVersionUID = 8031407455065485896L;
117 |
118 | @Override
119 | public Component createElement(final NavigationController controller, final NavigateableView view, int currentIndex, int totalCount) {
120 | final Button btn = new Button();
121 | btn.setStyleName(BaseTheme.BUTTON_LINK);
122 | btn.setSizeUndefined();
123 | btn.addStyleName(BREADCRUMB_ELEMENT);
124 | updateButtonTexts(btn, view);
125 |
126 | btn.addListener(new Button.ClickListener() {
127 |
128 | private static final long serialVersionUID = -9116612359809246223L;
129 |
130 | @Override
131 | public void buttonClick(ClickEvent event) {
132 | controller.getEventBus().fireEvent(view.getEventToShowThisView());
133 | }
134 | });
135 |
136 | return btn;
137 | }
138 |
139 | @Override
140 | public void updateButtonTexts(Button button, NavigateableView view) {
141 | button.setCaption(view.getBreadcrumbTitle());
142 | }
143 |
144 | @Override
145 | public void setBreadcrumbElementStyleName(String styleName) {
146 | BREADCRUMB_ELEMENT = styleName;
147 | }
148 | }
149 |
150 |
151 | private SeparatorFactory separatorFactory;
152 | private BreadcrumbElementFactory elementFactory;
153 | private final NavigationController navigationController;
154 |
155 | public Breadcrumbs(NavigationController controller){
156 | elementFactory = new DefaultBreadcrumbElementFactory();
157 | separatorFactory = new DefaultSeparatorFactory();
158 | this.setSizeUndefined();
159 | controller.addListener(this);
160 | this.navigationController = controller;
161 | this.setStyleName(BREADCRUMB_BAR);
162 | }
163 |
164 |
165 |
166 | /**
167 | * Set the position / alignment of the breadcrumbs-list in the whole {@link Breadcrumbs}
168 | * @param alignment {@link Alignment}
169 |
170 | public void setBreadcrumListAlignment(Alignment alignment){
171 | this.setComponentAlignment(breadcrumbElementContainer, alignment);
172 | }
173 | */
174 |
175 | /**
176 | * Returns the separator factory to use for creating separators between
177 | * breadcrumb buttons.
178 | */
179 | public SeparatorFactory getSeparatorFactory() {
180 | return separatorFactory;
181 | }
182 |
183 | /**
184 | * Sets the separator factory to use for creating separators between
185 | * breadcrumb buttons. Set this value to null to use the
186 | * default separator factory.
187 | */
188 | public void setSeparatorFactory(SeparatorFactory separatorFactory) {
189 | if (separatorFactory == null) {
190 | separatorFactory = new DefaultSeparatorFactory();
191 | }
192 | this.separatorFactory = separatorFactory;
193 | }
194 |
195 | /**
196 | * Returns the button factory to use for creating breadcrumb buttons.
197 | */
198 | public BreadcrumbElementFactory getBreadcrumbElementFactory() {
199 | return elementFactory;
200 | }
201 |
202 | /**
203 | * Sets the button factory to use for creating breadcrumb buttons. Set this
204 | * value to null to use the default button factory.
205 | */
206 | public void setBreadcrumbElementFactory(BreadcrumbElementFactory buttonFactory) {
207 | if (buttonFactory == null) {
208 | buttonFactory = new DefaultBreadcrumbElementFactory();
209 | }
210 | this.elementFactory = buttonFactory;
211 | }
212 |
213 |
214 | protected void addBreadcrumbForView(final NavigateableView view, int index, int totalCount) {
215 |
216 | final Component btn = getBreadcrumbElementFactory().createElement(navigationController, view, index, totalCount);
217 | this.addComponent(btn);
218 | // breadcrumbElementContainer.setComponentAlignment(btn, Alignment.MIDDLE_LEFT);
219 | }
220 |
221 |
222 |
223 |
224 |
225 | protected void addSeparatorForView() {
226 | Component separator = getSeparatorFactory().createSeparator();
227 | if (separator != null){
228 | this.addComponent(separator);
229 | // breadcrumbElementContainer.setComponentAlignment(separator, Alignment.MIDDLE_LEFT);
230 | }
231 | }
232 |
233 | protected void removeBreadcrumbs() {
234 | this.removeAllComponents();
235 |
236 | }
237 |
238 | @Override
239 | public void onNavigatedTo(NavigateableView view) {
240 | removeBreadcrumbs();
241 | generateBreadcrumb(view);
242 |
243 | }
244 |
245 | private void generateBreadcrumb(NavigateableView view){
246 |
247 | NavigateableView v = view;
248 | List viewPath = new LinkedList();
249 |
250 | // TODO optimization: do everything in one loop
251 | while (true){
252 |
253 | viewPath.add(v);
254 |
255 | if (v instanceof NavigateableSubView)
256 | {
257 | v = ((NavigateableSubView) v).getParentView();
258 | }
259 | else
260 | break;
261 | }
262 |
263 | int totalCount = viewPath.size();
264 | int index = 0;
265 | for (int i = viewPath.size()-1; i>=0; i--)
266 | {
267 | if (i!=viewPath.size()-1)
268 | addSeparatorForView();
269 |
270 | addBreadcrumbForView(viewPath.get(i), index, totalCount);
271 | index++;
272 | }
273 |
274 |
275 |
276 | }
277 |
278 | }
279 |
--------------------------------------------------------------------------------
/src/main/java/com/mvplite/event/EventBus.java:
--------------------------------------------------------------------------------
1 | package com.mvplite.event;
2 |
3 | import java.io.Serializable;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 | import java.util.LinkedHashSet;
7 | import java.util.Map;
8 | import java.util.Set;
9 | import java.util.concurrent.ConcurrentHashMap;
10 |
11 | /**
12 | * @author Hannes Dorfmann
13 | */
14 | class EventMethodCache implements Serializable {
15 |
16 |
17 | private static final long serialVersionUID = -3835595439788993624L;
18 |
19 | private final Map, Set> methodMap;
20 |
21 | public EventMethodCache() {
22 | methodMap = new ConcurrentHashMap<>();
23 | }
24 |
25 |
26 | /**
27 | * Adds a Method to the {@link #methodMap}
28 | */
29 | public void addMethodToCache(Class> c, Method m) {
30 | Set methods = methodMap.get(c);
31 |
32 | if (methods == null) {
33 | methods = new LinkedHashSet<>();
34 | methodMap.put(c, methods);
35 | }
36 |
37 | methods.add(m);
38 | }
39 |
40 |
41 | public void clear() {
42 | methodMap.clear();
43 | }
44 |
45 |
46 | public void removeCachedOf(Class> c) {
47 | methodMap.remove(c);
48 | }
49 |
50 |
51 | public Set getMethods(Class> c) {
52 | return methodMap.get(c);
53 | }
54 |
55 |
56 | public boolean isClassCached(Class> c) {
57 | return methodMap.containsKey(c);
58 | }
59 |
60 | }
61 |
62 | /**
63 | * The {@link EventDispatcher} is responsible for dispatching / delivering
64 | * a Event to the corresponding {@link EventHandler} - Method.
65 | * This is realized by using reflections, especially {@link Method#invoke(Object, Object...)}
66 | *
67 | * @author Hannes Dorfmann
68 | */
69 | class EventDispatcher implements Serializable {
70 |
71 | private static final long serialVersionUID = -7359501691640084178L;
72 |
73 | private final Object target;
74 | private final Method method;
75 |
76 |
77 | public EventDispatcher(Object target, Method method) {
78 | this.target = target;
79 | this.method = method;
80 | this.method.setAccessible(true);
81 | }
82 |
83 | public Object getTarget() {
84 | return target;
85 | }
86 |
87 | public void dispatchEvent(Object event) {
88 |
89 | try {
90 | method.invoke(target, event);
91 | } catch (IllegalArgumentException e) {
92 | throw new Error("Method rejected target/argument: " + event, e);
93 | } catch (IllegalAccessException e) {
94 | throw new Error("Method became inaccessible: " + event, e);
95 | } catch (InvocationTargetException e) {
96 | if (e.getCause() instanceof Error) {
97 | throw (Error) e.getCause();
98 | } else
99 | throw new Error(e);
100 | }
101 | }
102 |
103 | @Override
104 | public int hashCode() {
105 | final int PRIME = 31;
106 | return (PRIME + method.hashCode()) * PRIME
107 | + System.identityHashCode(target);
108 | }
109 |
110 | @Override
111 | public boolean equals(Object o) {
112 | if (o instanceof EventDispatcher) {
113 | EventDispatcher other = (EventDispatcher) o;
114 | return target == other.target && method.equals(other.method);
115 | }
116 |
117 | return false;
118 | }
119 | }
120 |
121 |
122 | /**
123 | * {@link Event}s can be fired to the {@link EventBus} and the {@link EventBus}
124 | * is the component, that deliver / dispatch the {@link Event}s to the registered {@link EventHandler}.
125 | *
126 | * @author Hannes Dorfmann
127 | * @see #fireEvent(Event)
128 | * @see #addHandler(Object)
129 | * @see #removeHandler(Object)
130 | */
131 | public class EventBus implements Serializable {
132 |
133 | private static final long serialVersionUID = 5500479291713928578L;
134 |
135 | private static final EventMethodCache eventMethodChache = new EventMethodCache();
136 | private final Map, Set> handlerMap;
137 |
138 | private static boolean caching = true;
139 |
140 |
141 | public EventBus() {
142 | handlerMap = new ConcurrentHashMap<>();
143 | }
144 |
145 | /**
146 | * Enable or disable caching
147 | */
148 | public void setUseCache(boolean caching) {
149 | EventBus.caching = caching;
150 | }
151 |
152 | /**
153 | * Get the Class of the {@link Event}.
154 | * The passed {@link Method} must be a valid {@link EventHandler}-annotated
155 | * method with exactly one parameter (the {@link Event}).
156 | * This method is a little helper method and is only used by the {@link EventBus} internally.
157 | */
158 | @SuppressWarnings("unchecked")
159 | private Class extends Event> getEventClass(Method m) {
160 | return (Class extends Event>) m.getParameterTypes()[0];
161 | }
162 |
163 | /**
164 | * Registers an {@link EventHandler}
165 | */
166 | public void addHandler(Object handler) {
167 |
168 | boolean added;
169 |
170 | if (caching) {
171 | if (!eventMethodChache.isClassCached(handler.getClass()))
172 | added = scanHandlerAndCreateEventDispatcher(handler); // This class is not cached, so scan the class
173 | else
174 | // This class has been cached (has been already scanned), so build the EventDispatcher from Cache
175 | added = createEventDispatchersFromCache(handler);
176 | } else
177 | added = scanHandlerAndCreateEventDispatcher(handler);
178 |
179 | if (!added)
180 | throw new Error("No @EventHandler annotated Method found in " + handler.getClass().getName());
181 |
182 | }
183 |
184 |
185 | private boolean scanHandlerAndCreateEventDispatcher(Object handler) {
186 | boolean added = false;
187 | for (Method m : handler.getClass().getMethods()) {
188 | if (!m.isAnnotationPresent(EventHandler.class))
189 | continue;
190 |
191 | Class> params[] = m.getParameterTypes();
192 | if (params.length == 1 && isEventClass(params[0])) {
193 | // This Method is a Valid EventHandler
194 | EventDispatcher disp = new EventDispatcher(handler, m);
195 | addEventDispatcher(getEventClass(m), disp);
196 | added = true;
197 |
198 | if (caching)
199 | eventMethodChache.addMethodToCache(handler.getClass(), m);
200 | } else
201 | throw new Error("You have annotated the Method " + m.getName() + " with @EventHandler, " +
202 | "but this method did not match the required one Parameter (exactly one) of the type Event");
203 | }
204 |
205 | return added;
206 | }
207 |
208 |
209 | private boolean isEventClass(Class> clazz) {
210 |
211 | Class> c = clazz;
212 | while (c != null) {
213 | if (c.equals(Event.class))
214 | return true;
215 |
216 | c = c.getSuperclass();
217 | }
218 |
219 | return false;
220 |
221 | }
222 |
223 | /**
224 | * Creates {@link EventDispatcher}s by unsing the {@link EventMethodCache}.
225 | * That means, that the class of the passed handler has already be scanned for
226 | * {@link EventHandler} annotations and all information about building the
227 | * {@link EventDispatcher}s are present in the {@link EventMethodCache}.
228 | */
229 | private boolean createEventDispatchersFromCache(Object handler) {
230 |
231 | Set methods = eventMethodChache.getMethods(handler.getClass());
232 |
233 | if (methods == null)
234 | throw new Error("The class " + handler.getClass().getName() + " has not been cached until now. However the EventBus tries to create a EventDispatcher from the cache.");
235 |
236 | for (Method m : methods) {
237 | EventDispatcher disp = new EventDispatcher(handler, m);
238 | addEventDispatcher(getEventClass(m), disp);
239 | }
240 |
241 | return !methods.isEmpty();
242 | }
243 |
244 | /**
245 | * Add a {@link EventDispatcher} for the passed {@link Event}-Class
246 | */
247 | private void addEventDispatcher(Class extends Event> eventClass,
248 | EventDispatcher disp) {
249 |
250 | Set dispatchers = handlerMap.get(eventClass);
251 |
252 | if (dispatchers == null) {
253 | dispatchers = new LinkedHashSet<>();
254 | handlerMap.put(eventClass, dispatchers);
255 | }
256 |
257 | dispatchers.add(disp);
258 | }
259 |
260 |
261 | /**
262 | * Removes an Handler (a Object with {@link EventHandler} annotated methods) from
263 | * the {@link EventBus}. That means, that future fired {@link Event}s are no longer
264 | * dispatched / delivered to the passed handler
265 | */
266 | public void removeHandler(Object handler) {
267 |
268 | Set toRemove = new LinkedHashSet<>();
269 | for (Set dispatchers : handlerMap.values()) {
270 | for (EventDispatcher d : dispatchers)
271 | if (d.getTarget() == handler)
272 | toRemove.add(d);
273 |
274 | dispatchers.removeAll(toRemove);
275 | toRemove.clear();
276 | }
277 | }
278 |
279 |
280 | /**
281 | * Fires a Event to the EventBus to inform all registered EventHandlers about this Event
282 | *
283 | * @return true if at least one {@link EventHandler} is registered and has received the passed event,
284 | * otherwise false
285 | */
286 | public boolean fireEvent(Event event) {
287 |
288 | Set dispatchers = handlerMap.get(event.getClass());
289 | if (dispatchers == null || dispatchers.isEmpty())
290 | return false;
291 |
292 | for (EventDispatcher disp : dispatchers)
293 | disp.dispatchEvent(event);
294 |
295 | return true;
296 | }
297 |
298 |
299 | }
300 |
--------------------------------------------------------------------------------