001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.gwt.ui;
021    
022    import com.google.gwt.event.dom.client.ClickEvent;
023    import com.google.gwt.event.dom.client.ClickHandler;
024    import com.google.gwt.user.client.ui.*;
025    import com.google.gwt.widgetideas.table.client.PreloadedTable;
026    import org.sonar.wsclient.gwt.AbstractCallback;
027    import org.sonar.wsclient.gwt.Sonar;
028    import org.sonar.wsclient.services.Resource;
029    import org.sonar.wsclient.services.Source;
030    import org.sonar.wsclient.services.SourceQuery;
031    
032    import java.util.*;
033    
034    public abstract class SourcePanel extends Composite implements ClickHandler {
035    
036      private static final int MAX_LINES_BY_BLOCK = 3000;
037    
038      private final Panel panel = new VerticalPanel();
039      private PreloadedTable rowsTable;
040      private Source source;
041      private final Loading loading = new Loading();
042      private int from = 0;
043      private int length = 0;
044    
045      private boolean started = false;
046      private Resource resource;
047      private boolean hasNoSources = false;
048    
049      // pagination
050      private Button moreButton = null;
051      private int currentRow = 0;
052      private int offset = 0;
053      private boolean firstPage = true;
054      private boolean previousLineIsDecorated = true;
055      private boolean firstDecoratedLine = true;
056    
057      public SourcePanel(Resource resource) {
058        this(resource, 0, 0);
059      }
060    
061      public Resource getResource() {
062        return resource;
063      }
064    
065      public SourcePanel(Resource resource, int from, int length) {
066        this.from = from;
067        this.length = length;
068        this.resource = resource;
069    
070        panel.add(loading);
071        panel.getElement().setId("sourcePanel");
072        initWidget(panel);
073        setStyleName("gwt-SourcePanel");
074    
075        loadSources();
076      }
077    
078      public void onClick(ClickEvent clickEvent) {
079        if (clickEvent.getSource() == moreButton) {
080          hideMoreButton();
081          displaySource();
082        }
083      }
084    
085      public Button getMoreButton() {
086        if (moreButton == null) {
087          moreButton = new Button("More");
088          moreButton.getElement().setId("more_source");
089          moreButton.addClickHandler(this);
090        }
091        return moreButton;
092      }
093    
094      public void showMoreButton() {
095        hideMoreButton();
096        panel.add(getMoreButton());
097      }
098    
099      public void hideMoreButton() {
100        getMoreButton().removeFromParent();
101      }
102    
103    
104      public void refresh() {
105        if (!hasNoSources) {
106          panel.clear();
107          panel.add(loading);
108    
109          rowsTable = null;
110          currentRow = 0;
111          offset = 0;
112          firstPage = true;
113          previousLineIsDecorated = true;
114          firstDecoratedLine = true;
115          displaySource();
116        }
117      }
118    
119      private void loadSources() {
120        Sonar.getInstance().find(SourceQuery.create(resource.getId().toString())
121            .setLinesFromLine(from, length)
122            .setHighlightedSyntax(true), new AbstractCallback<Source>(loading) {
123    
124          @Override
125          protected void doOnResponse(Source result) {
126            source = result;
127            displaySource();
128          }
129    
130          @Override
131          protected void doOnError(int errorCode, String errorMessage) {
132            if (errorCode == 404) {
133              panel.add(new HTML("<p style=\"padding: 5px\">No sources</p>"));
134              hasNoSources = true;
135              loading.removeFromParent();
136    
137            } else if (errorCode == 401) {
138              panel.add(new HTML("<p style=\"padding: 5px\">You're not authorized to view source code</p>"));
139              hasNoSources = true;
140              loading.removeFromParent();
141    
142            } else {
143              super.onError(errorCode, errorMessage);
144            }
145          }
146    
147        });
148      }
149    
150      protected void setStarted() {
151        started = true;
152        displaySource();
153      }
154    
155      private void displaySource() {
156        if (started && source != null) {
157          createRowsTable();
158    
159          int displayedLines = 0;
160          SortedMap<Integer, String> lines = source.getLinesById().subMap(offset, offset + source.getLinesById().lastKey() + 1);
161          Iterator<Map.Entry<Integer, String>> linesById = lines.entrySet().iterator();
162    
163          while (displayedLines < MAX_LINES_BY_BLOCK && linesById.hasNext()) {
164            Map.Entry<Integer, String> entry = linesById.next();
165            Integer lineIndex = entry.getKey();
166            if (shouldDecorateLine(lineIndex)) {
167              if (!previousLineIsDecorated && !firstDecoratedLine) {
168                setRowHtml(0, "<div class='src' style='background-color: #fff;height: 3em; border-top: 1px dashed silver;border-bottom: 1px dashed silver;'> </div>");
169                setRowHtml(1, " ");
170                setRowHtml(2, " ");
171                setRowHtml(3, "<div class='src' style='background-color: #fff;height: 3em; border-top: 1px dashed silver;border-bottom: 1px dashed silver;'> </div>");
172                currentRow++;
173              }
174    
175              List<Row> rows = decorateLine(lineIndex, entry.getValue());
176              if (rows != null) {
177                for (Row row : rows) {
178                  setRowHtml(0, row.getColumn1());
179                  setRowHtml(1, row.getColumn2());
180                  setRowHtml(2, row.getColumn3());
181                  setRowHtml(3, row.getColumn4());
182                  currentRow++;
183                }
184                previousLineIsDecorated = true;
185                firstDecoratedLine = false;
186              }
187              displayedLines++;
188    
189            } else {
190              previousLineIsDecorated = false;
191            }
192            offset++;
193          }
194    
195          if (firstPage) {
196            panel.clear();
197            panel.add(rowsTable);
198            firstPage = false;
199          }
200    
201          if (offset <= source.getLinesById().lastKey()) {
202            showMoreButton();
203          } else {
204            hideMoreButton();
205          }
206        }
207      }
208    
209      private void setRowHtml(int colNum, String html) {
210        if (firstPage) {
211          rowsTable.setPendingHTML(currentRow, colNum, html);
212        } else {
213          rowsTable.setHTML(currentRow, colNum, html);
214        }
215      }
216    
217      private void createRowsTable() {
218        if (rowsTable == null) {
219          rowsTable = new PreloadedTable();
220          rowsTable.setStyleName("sources code");
221    
222          offset = source.getLinesById().firstKey();
223          if (shouldDecorateLine(0)) {
224            List<Row> rows = decorateLine(0, null);
225            if (rows != null) {
226              for (Row row : rows) {
227                rowsTable.setPendingHTML(currentRow, 0, row.getColumn1());
228                rowsTable.setPendingHTML(currentRow, 1, row.getColumn2());
229                rowsTable.setPendingHTML(currentRow, 2, row.getColumn3());
230                rowsTable.setPendingHTML(currentRow, 3, row.getColumn4());
231                currentRow++;
232              }
233            }
234          }
235        }
236      }
237    
238      protected boolean shouldDecorateLine(int index) {
239        return true;
240      }
241    
242      protected List<Row> decorateLine(int index, String source) {
243        if (index > 0) {
244          return Arrays.asList(new Row(index, source));
245        }
246        return null;
247      }
248    
249      public static class Row {
250        protected String column1;
251        protected String column2;
252        protected String column3;
253        protected String column4;
254    
255        public Row(String column1, String column2, String column3) {
256          this.column1 = column1;
257          this.column2 = column2;
258          this.column3 = column3;
259          this.column4 = "";
260        }
261    
262        public Row(String column1, String column2, String column3, String column4) {
263          this.column1 = column1;
264          this.column2 = column2;
265          this.column3 = column3;
266          this.column4 = column4;
267        }
268    
269        public Row(int lineIndex, String source) {
270          setLineIndex(lineIndex, "");
271          unsetValue();
272          setSource(source, "");
273        }
274    
275        public Row() {
276        }
277    
278        public Row setLineIndex(int index, String style) {
279          column1 = "<div class='ln " + style + "'>" + index + "</div>";
280          return this;
281        }
282    
283        public Row setValue(String value, String style) {
284          column2 = "<div class='val " + style + "'>" + value + "</div>";
285          return this;
286        }
287    
288        public Row setValue2(String value, String style) {
289          column3 = "<div class='val " + style + "'>" + value + "</div>";
290          return this;
291        }
292    
293        public Row unsetValue() {
294          column2 = "";
295          column3 = "";
296          return this;
297        }
298    
299        public Row setSource(String source, String style) {
300          column4 = "<div class='src " + style + "'><pre>" + source + "</pre></div>";
301          return this;
302        }
303    
304        public String getColumn1() {
305          return column1;
306        }
307    
308        public void setColumn1(String column1) {
309          this.column1 = column1;
310        }
311    
312        public String getColumn2() {
313          return column2;
314        }
315    
316        public void setColumn2(String column2) {
317          this.column2 = column2;
318        }
319    
320        public String getColumn3() {
321          return column3;
322        }
323    
324        public void setColumn3(String column3) {
325          this.column3 = column3;
326        }
327    
328        public String getColumn4() {
329          return column4;
330        }
331    
332        public void setColumn4(String column4) {
333          this.column4 = column4;
334        }
335      }
336    }