fork download
  1. /*
  2.  * opsu! - an open-source osu! client
  3.  * Copyright (C) 2014, 2015 Jeffrey Han
  4.  *
  5.  * opsu! is free software: you can redistribute it and/or modify
  6.  * it under the terms of the GNU General Public License as published by
  7.  * the Free Software Foundation, either version 3 of the License, or
  8.  * (at your option) any later version.
  9.  *
  10.  * opsu! is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with opsu!. If not, see <http://w...content-available-to-author-only...u.org/licenses/>.
  17.  */
  18.  
  19. package itdelatrisu.opsu.beatmap;
  20.  
  21. import itdelatrisu.opsu.ErrorHandler;
  22. import itdelatrisu.opsu.Options;
  23. import itdelatrisu.opsu.Utils;
  24. import itdelatrisu.opsu.db.BeatmapDB;
  25. import itdelatrisu.opsu.io.MD5InputStreamWrapper;
  26.  
  27. import java.io.BufferedInputStream;
  28. import java.io.BufferedReader;
  29. import java.io.File;
  30. import java.io.FileInputStream;
  31. import java.io.FileReader;
  32. import java.io.FilenameFilter;
  33. import java.io.IOException;
  34. import java.io.InputStream;
  35. import java.io.InputStreamReader;
  36. import java.security.NoSuchAlgorithmException;
  37. import java.util.ArrayList;
  38. import java.util.Collections;
  39. import java.util.HashMap;
  40. import java.util.LinkedList;
  41. import java.util.List;
  42. import java.util.Map;
  43.  
  44. import org.newdawn.slick.Color;
  45. import org.newdawn.slick.util.Log;
  46.  
  47. /**
  48.  * Parser for beatmaps.
  49.  */
  50. public class BeatmapParser {
  51. /** The string lookup database. */
  52. private static HashMap<String, String> stringdb = new HashMap<String, String>();
  53.  
  54. /** The expected pattern for beatmap directories, used to find beatmap set IDs. */
  55. private static final String DIR_MSID_PATTERN = "^\\d+ .*";
  56.  
  57. /** The current file being parsed. */
  58. private static File currentFile;
  59.  
  60. /** The current directory number while parsing. */
  61. private static int currentDirectoryIndex = -1;
  62.  
  63. /** The total number of directories to parse. */
  64. private static int totalDirectories = -1;
  65.  
  66. /** Parser statuses. */
  67. public enum Status { NONE, PARSING, CACHE, INSERTING };
  68.  
  69. /** The current status. */
  70. private static Status status = Status.NONE;
  71.  
  72. /** If no Provider supports a MessageDigestSpi implementation for the MD5 algorithm. */
  73. private static boolean hasNoMD5Algorithm = false;
  74.  
  75. // This class should not be instantiated.
  76. private BeatmapParser() {}
  77.  
  78. /**
  79. * Invokes parser for each OSU file in a root directory and
  80. * adds the beatmaps to a new BeatmapSetList.
  81. * @param root the root directory (search has depth 1)
  82. */
  83. public static void parseAllFiles(File root) {
  84. // create a new BeatmapSetList
  85. BeatmapSetList.create();
  86.  
  87. // create a new watch service
  88. if (Options.isWatchServiceEnabled())
  89. BeatmapWatchService.create();
  90.  
  91. // parse all directories
  92. parseDirectories(root.listFiles());
  93. }
  94.  
  95. /**
  96. * Invokes parser for each directory in the given array and
  97. * adds the beatmaps to the existing BeatmapSetList.
  98. * @param dirs the array of directories to parse
  99. * @return the last BeatmapSetNode parsed, or null if none
  100. */
  101. public static BeatmapSetNode parseDirectories(File[] dirs) {
  102. if (dirs == null)
  103. return null;
  104.  
  105. // progress tracking
  106. status = Status.PARSING;
  107. currentDirectoryIndex = 0;
  108. totalDirectories = dirs.length;
  109.  
  110. // get last modified map from database
  111. Map<String, Long> map = BeatmapDB.getLastModifiedMap();
  112.  
  113. // beatmap lists
  114. List<ArrayList<Beatmap>> allBeatmaps = new LinkedList<ArrayList<Beatmap>>();
  115. List<Beatmap> cachedBeatmaps = new LinkedList<Beatmap>(); // loaded from database
  116. List<Beatmap> parsedBeatmaps = new LinkedList<Beatmap>(); // loaded from parser
  117.  
  118. // watch service
  119. BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null;
  120.  
  121. // parse directories
  122. BeatmapSetNode lastNode = null;
  123. for (File dir : dirs) {
  124. currentDirectoryIndex++;
  125. if (!dir.isDirectory())
  126. continue;
  127.  
  128. // find all OSU files
  129. File[] files = dir.listFiles(new FilenameFilter() {
  130. @Override
  131. public boolean accept(File dir, String name) {
  132. return name.toLowerCase().endsWith(".osu");
  133. }
  134. });
  135. if (files == null || files.length < 1)
  136. continue;
  137.  
  138. // create a new group entry
  139. ArrayList<Beatmap> beatmaps = new ArrayList<Beatmap>();
  140. for (File file : files) {
  141. currentFile = file;
  142.  
  143. // check if beatmap is cached
  144. String path = String.format("%s/%s", dir.getName(), file.getName());
  145. if (map != null) {
  146. Long lastModified = map.get(path);
  147. if (lastModified != null) {
  148. // check last modified times
  149. if (lastModified == file.lastModified()) {
  150. // add to cached beatmap list
  151. Beatmap beatmap = new Beatmap(file);
  152. beatmaps.add(beatmap);
  153. cachedBeatmaps.add(beatmap);
  154. continue;
  155. } else
  156. BeatmapDB.delete(dir.getName(), file.getName());
  157. }
  158. }
  159.  
  160. // Parse hit objects only when needed to save time/memory.
  161. // Change boolean to 'true' to parse them immediately.
  162. Beatmap beatmap = parseFile(file, dir, beatmaps, false);
  163.  
  164. // add to parsed beatmap list
  165. if (beatmap != null) {
  166. beatmaps.add(beatmap);
  167. parsedBeatmaps.add(beatmap);
  168. }
  169. }
  170.  
  171. // add group entry if non-empty
  172. if (!beatmaps.isEmpty()) {
  173. beatmaps.trimToSize();
  174. allBeatmaps.add(beatmaps);
  175. if (ws != null)
  176. ws.registerAll(dir.toPath());
  177. }
  178.  
  179. // stop parsing files (interrupted)
  180. if (Thread.interrupted())
  181. break;
  182. }
  183.  
  184. // load cached entries from database
  185. if (!cachedBeatmaps.isEmpty()) {
  186. status = Status.CACHE;
  187.  
  188. // Load array fields only when needed to save time/memory.
  189. // Change flag to 'LOAD_ALL' to load them immediately.
  190. BeatmapDB.load(cachedBeatmaps, BeatmapDB.LOAD_NONARRAY);
  191. }
  192.  
  193. // add group entries to BeatmapSetList
  194. for (ArrayList<Beatmap> beatmaps : allBeatmaps) {
  195. Collections.sort(beatmaps);
  196. lastNode = BeatmapSetList.get().addSongGroup(beatmaps);
  197. }
  198.  
  199. // clear string DB
  200. stringdb = new HashMap<String, String>();
  201.  
  202. // add beatmap entries to database
  203. if (!parsedBeatmaps.isEmpty()) {
  204. status = Status.INSERTING;
  205. BeatmapDB.insert(parsedBeatmaps);
  206. }
  207.  
  208. status = Status.NONE;
  209. currentFile = null;
  210. currentDirectoryIndex = -1;
  211. totalDirectories = -1;
  212. return lastNode;
  213. }
  214.  
  215. /**
  216. * Parses a beatmap.
  217. * @param file the file to parse
  218. * @param dir the directory containing the beatmap
  219. * @param beatmaps the song group
  220. * @param parseObjects if true, hit objects will be fully parsed now
  221. * @return the new beatmap
  222. */
  223. private static Beatmap parseFile(File file, File dir, ArrayList<Beatmap> beatmaps, boolean parseObjects) {
  224. Beatmap beatmap = new Beatmap(file);
  225. beatmap.timingPoints = new ArrayList<TimingPoint>();
  226.  
  227. try (
  228. MD5InputStreamWrapper md5stream = (!hasNoMD5Algorithm) ? new MD5InputStreamWrapper(bis) : null;
  229. BufferedReader in = new BufferedReader(new InputStreamReader((md5stream != null) ? md5stream : bis, "UTF-8"));
  230. ) {
  231. String line = in.readLine();
  232. String tokens[] = null;
  233. while (line != null) {
  234. line = line.trim();
  235. if (!isValidLine(line)) {
  236. line = in.readLine();
  237. continue;
  238. }
  239. switch (line) {
  240. case "[General]":
  241. while ((line = in.readLine()) != null) {
  242. line = line.trim();
  243. if (!isValidLine(line))
  244. continue;
  245. if (line.charAt(0) == '[')
  246. break;
  247. if ((tokens = tokenize(line)) == null)
  248. continue;
  249. try {
  250. switch (tokens[0]) {
  251. case "AudioFilename":
  252. File audioFileName = new File(dir, tokens[1]);
  253. if (!beatmaps.isEmpty()) {
  254. // if possible, reuse the same File object from another Beatmap in the group
  255. File groupAudioFileName = beatmaps.get(0).audioFilename;
  256. if (groupAudioFileName != null &&
  257. tokens[1].equalsIgnoreCase(groupAudioFileName.getName()))
  258. audioFileName = groupAudioFileName;
  259. }
  260. if (!audioFileName.isFile()) {
  261. // try to find the file with a case-insensitive match
  262. boolean match = false;
  263. for (String s : dir.list()) {
  264. if (s.equalsIgnoreCase(tokens[1])) {
  265. audioFileName = new File(dir, s);
  266. match = true;
  267. break;
  268. }
  269. }
  270. if (!match) {
  271. Log.error(String.format("Audio file '%s' not found in directory '%s'.", tokens[1], dir.getName()));
  272. return null;
  273. }
  274. }
  275. beatmap.audioFilename = audioFileName;
  276. break;
  277. case "AudioLeadIn":
  278. beatmap.audioLeadIn = Integer.parseInt(tokens[1]);
  279. break;
  280. // case "AudioHash": // deprecated
  281. // beatmap.audioHash = tokens[1];
  282. // break;
  283. case "PreviewTime":
  284. beatmap.previewTime = Integer.parseInt(tokens[1]);
  285. break;
  286. case "Countdown":
  287. beatmap.countdown = Byte.parseByte(tokens[1]);
  288. break;
  289. case "SampleSet":
  290. beatmap.sampleSet = getDBString(tokens[1]);
  291. break;
  292. case "StackLeniency":
  293. beatmap.stackLeniency = Float.parseFloat(tokens[1]);
  294. break;
  295. case "Mode":
  296. beatmap.mode = Byte.parseByte(tokens[1]);
  297.  
  298. /* Non-Opsu! standard files not implemented (obviously). */
  299. if (beatmap.mode != Beatmap.MODE_OSU)
  300. return null;
  301.  
  302. break;
  303. case "LetterboxInBreaks":
  304. beatmap.letterboxInBreaks = Utils.parseBoolean(tokens[1]);
  305. break;
  306. case "WidescreenStoryboard":
  307. beatmap.widescreenStoryboard = Utils.parseBoolean(tokens[1]);
  308. break;
  309. case "EpilepsyWarning":
  310. beatmap.epilepsyWarning = Utils.parseBoolean(tokens[1]);
  311. default:
  312. break;
  313. }
  314. } catch (Exception e) {
  315. Log.warn(String.format("Failed to read line '%s' for file '%s'.",
  316. line, file.getAbsolutePath()), e);
  317. }
  318. }
  319. break;
  320. case "[Editor]":
  321. while ((line = in.readLine()) != null) {
  322. line = line.trim();
  323. if (!isValidLine(line))
  324. continue;
  325. if (line.charAt(0) == '[')
  326. break;
  327. /* Not implemented. */
  328. // if ((tokens = tokenize(line)) == null)
  329. // continue;
  330. // try {
  331. // switch (tokens[0]) {
  332. // case "Bookmarks":
  333. // String[] bookmarks = tokens[1].split(",");
  334. // beatmap.bookmarks = new int[bookmarks.length];
  335. // for (int i = 0; i < bookmarks.length; i++)
  336. // osu.bookmarks[i] = Integer.parseInt(bookmarks[i]);
  337. // break;
  338. // case "DistanceSpacing":
  339. // beatmap.distanceSpacing = Float.parseFloat(tokens[1]);
  340. // break;
  341. // case "BeatDivisor":
  342. // beatmap.beatDivisor = Byte.parseByte(tokens[1]);
  343. // break;
  344. // case "GridSize":
  345. // beatmap.gridSize = Integer.parseInt(tokens[1]);
  346. // break;
  347. // case "TimelineZoom":
  348. // beatmap.timelineZoom = Integer.parseInt(tokens[1]);
  349. // break;
  350. // default:
  351. // break;
  352. // }
  353. // } catch (Exception e) {
  354. // Log.warn(String.format("Failed to read editor line '%s' for file '%s'.",
  355. // line, file.getAbsolutePath()), e);
  356. // }
  357. }
  358. break;
  359. case "[Metadata]":
  360. while ((line = in.readLine()) != null) {
  361. line = line.trim();
  362. if (!isValidLine(line))
  363. continue;
  364. if (line.charAt(0) == '[')
  365. break;
  366. if ((tokens = tokenize(line)) == null)
  367. continue;
  368. try {
  369. switch (tokens[0]) {
  370. case "Title":
  371. beatmap.title = getDBString(tokens[1]);
  372. break;
  373. case "TitleUnicode":
  374. beatmap.titleUnicode = getDBString(tokens[1]);
  375. break;
  376. case "Artist":
  377. beatmap.artist = getDBString(tokens[1]);
  378. break;
  379. case "ArtistUnicode":
  380. beatmap.artistUnicode = getDBString(tokens[1]);
  381. break;
  382. case "Creator":
  383. beatmap.creator = getDBString(tokens[1]);
  384. break;
  385. case "Version":
  386. beatmap.version = getDBString(tokens[1]);
  387. break;
  388. case "Source":
  389. beatmap.source = getDBString(tokens[1]);
  390. break;
  391. case "Tags":
  392. beatmap.tags = getDBString(tokens[1].toLowerCase());
  393. break;
  394. case "BeatmapID":
  395. beatmap.beatmapID = Integer.parseInt(tokens[1]);
  396. break;
  397. case "BeatmapSetID":
  398. beatmap.beatmapSetID = Integer.parseInt(tokens[1]);
  399. break;
  400. }
  401. } catch (Exception e) {
  402. Log.warn(String.format("Failed to read metadata '%s' for file '%s'.",
  403. line, file.getAbsolutePath()), e);
  404. }
  405. if (beatmap.beatmapSetID <= 0) { // try to determine MSID from directory name
  406. if (dir != null && dir.isDirectory()) {
  407. String dirName = dir.getName();
  408. if (!dirName.isEmpty() && dirName.matches(DIR_MSID_PATTERN))
  409. beatmap.beatmapSetID = Integer.parseInt(dirName.substring(0, dirName.indexOf(' ')));
  410. }
  411. }
  412. }
  413. break;
  414. case "[Difficulty]":
  415. while ((line = in.readLine()) != null) {
  416. line = line.trim();
  417. if (!isValidLine(line))
  418. continue;
  419. if (line.charAt(0) == '[')
  420. break;
  421. if ((tokens = tokenize(line)) == null)
  422. continue;
  423. try {
  424. switch (tokens[0]) {
  425. case "HPDrainRate":
  426. beatmap.HPDrainRate = Float.parseFloat(tokens[1]);
  427. break;
  428. case "CircleSize":
  429. beatmap.circleSize = Float.parseFloat(tokens[1]);
  430. break;
  431. case "OverallDifficulty":
  432. beatmap.overallDifficulty = Float.parseFloat(tokens[1]);
  433. break;
  434. case "ApproachRate":
  435. beatmap.approachRate = Float.parseFloat(tokens[1]);
  436. break;
  437. case "SliderMultiplier":
  438. beatmap.sliderMultiplier = Float.parseFloat(tokens[1]);
  439. break;
  440. case "SliderTickRate":
  441. beatmap.sliderTickRate = Float.parseFloat(tokens[1]);
  442. break;
  443. }
  444. } catch (Exception e) {
  445. Log.warn(String.format("Failed to read difficulty '%s' for file '%s'.",
  446. line, file.getAbsolutePath()), e);
  447. }
  448. }
  449. if (beatmap.approachRate == -1f) // not in old format
  450. beatmap.approachRate = beatmap.overallDifficulty;
  451. break;
  452. case "[Events]":
  453. while ((line = in.readLine()) != null) {
  454. line = line.trim();
  455. if (!isValidLine(line))
  456. continue;
  457. if (line.charAt(0) == '[')
  458. break;
  459. tokens = line.split(",");
  460. switch (tokens[0]) {
  461. case "0": // background
  462. tokens[2] = tokens[2].replaceAll("^\"|\"$", "");
  463. String ext = BeatmapParser.getExtension(tokens[2]);
  464. if (ext.equals("jpg") || ext.equals("png"))
  465. beatmap.bg = new File(dir, getDBString(tokens[2]));
  466. break;
  467. case "2": // break periods
  468. try {
  469. if (beatmap.breaks == null) // optional, create if needed
  470. beatmap.breaks = new ArrayList<Integer>();
  471. beatmap.breaks.add(Integer.parseInt(tokens[1]));
  472. beatmap.breaks.add(Integer.parseInt(tokens[2]));
  473. } catch (Exception e) {
  474. Log.warn(String.format("Failed to read break period '%s' for file '%s'.",
  475. line, file.getAbsolutePath()), e);
  476. }
  477. break;
  478. default:
  479. /* Not implemented. */
  480. break;
  481. }
  482. }
  483. if (beatmap.breaks != null)
  484. beatmap.breaks.trimToSize();
  485. break;
  486. case "[TimingPoints]":
  487. while ((line = in.readLine()) != null) {
  488. line = line.trim();
  489. if (!isValidLine(line))
  490. continue;
  491. if (line.charAt(0) == '[')
  492. break;
  493.  
  494. try {
  495. // parse timing point
  496. TimingPoint timingPoint = new TimingPoint(line);
  497.  
  498. // calculate BPM
  499. if (!timingPoint.isInherited()) {
  500. int bpm = Math.round(60000 / timingPoint.getBeatLength());
  501. if (beatmap.bpmMin == 0)
  502. beatmap.bpmMin = beatmap.bpmMax = bpm;
  503. else if (bpm < beatmap.bpmMin)
  504. beatmap.bpmMin = bpm;
  505. else if (bpm > beatmap.bpmMax)
  506. beatmap.bpmMax = bpm;
  507. }
  508.  
  509. beatmap.timingPoints.add(timingPoint);
  510. } catch (Exception e) {
  511. Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
  512. line, file.getAbsolutePath()), e);
  513. }
  514. }
  515. beatmap.timingPoints.trimToSize();
  516. break;
  517. case "[Colours]":
  518. LinkedList<Color> colors = new LinkedList<Color>();
  519. while ((line = in.readLine()) != null) {
  520. line = line.trim();
  521. if (!isValidLine(line))
  522. continue;
  523. if (line.charAt(0) == '[')
  524. break;
  525. if ((tokens = tokenize(line)) == null)
  526. continue;
  527. try {
  528. String[] rgb = tokens[1].split(",");
  529. Color color = new Color(
  530. Integer.parseInt(rgb[0]),
  531. Integer.parseInt(rgb[1]),
  532. Integer.parseInt(rgb[2])
  533. );
  534. switch (tokens[0]) {
  535. case "Combo1":
  536. case "Combo2":
  537. case "Combo3":
  538. case "Combo4":
  539. case "Combo5":
  540. case "Combo6":
  541. case "Combo7":
  542. case "Combo8":
  543. colors.add(color);
  544. break;
  545. case "SliderBorder":
  546. beatmap.sliderBorder = color;
  547. break;
  548. default:
  549. break;
  550. }
  551. } catch (Exception e) {
  552. Log.warn(String.format("Failed to read color '%s' for file '%s'.",
  553. line, file.getAbsolutePath()), e);
  554. }
  555. }
  556. if (!colors.isEmpty())
  557. beatmap.combo = colors.toArray(new Color[colors.size()]);
  558. break;
  559. case "[HitObjects]":
  560. int type = 0;
  561. while ((line = in.readLine()) != null) {
  562. line = line.trim();
  563. if (!isValidLine(line))
  564. continue;
  565. if (line.charAt(0) == '[')
  566. break;
  567. /* Only type counts parsed at this time. */
  568. tokens = line.split(",");
  569. try {
  570. type = Integer.parseInt(tokens[3]);
  571. if ((type & HitObject.TYPE_CIRCLE) > 0)
  572. beatmap.hitObjectCircle++;
  573. else if ((type & HitObject.TYPE_SLIDER) > 0)
  574. beatmap.hitObjectSlider++;
  575. else //if ((type & HitObject.TYPE_SPINNER) > 0)
  576. beatmap.hitObjectSpinner++;
  577. } catch (Exception e) {
  578. Log.warn(String.format("Failed to read hit object '%s' for file '%s'.",
  579. line, file.getAbsolutePath()), e);
  580. }
  581. }
  582.  
  583. try {
  584. // map length = last object end time (TODO: end on slider?)
  585. if ((type & HitObject.TYPE_SPINNER) > 0) {
  586. // some 'endTime' fields contain a ':' character (?)
  587. int index = tokens[5].indexOf(':');
  588. if (index != -1)
  589. tokens[5] = tokens[5].substring(0, index);
  590. beatmap.endTime = Integer.parseInt(tokens[5]);
  591. } else if (type != 0)
  592. beatmap.endTime = Integer.parseInt(tokens[2]);
  593. } catch (Exception e) {
  594. Log.warn(String.format("Failed to read hit object end time '%s' for file '%s'.",
  595. line, file.getAbsolutePath()), e);
  596. }
  597. break;
  598. default:
  599. line = in.readLine();
  600. break;
  601. }
  602. }
  603. if (md5stream != null)
  604. beatmap.md5Hash = md5stream.getMD5();
  605. } catch (IOException e) {
  606. ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false);
  607. ErrorHandler.error("Failed to get MD5 hash stream.", e, true);
  608.  
  609. // retry without MD5
  610. hasNoMD5Algorithm = true;
  611. return parseFile(file, dir, beatmaps, parseObjects);
  612. }
  613.  
  614. // no associated audio file?
  615. if (beatmap.audioFilename == null)
  616. return null;
  617.  
  618. // parse hit objects now?
  619. if (parseObjects)
  620. parseHitObjects(beatmap);
  621.  
  622. return beatmap;
  623. }
  624.  
  625. /**
  626. * Parses all hit objects in a beatmap.
  627. * @param beatmap the beatmap to parse
  628. */
  629. public static void parseHitObjects(Beatmap beatmap) {
  630. if (beatmap.objects != null) // already parsed
  631. return;
  632.  
  633. beatmap.objects = new HitObject[(beatmap.hitObjectCircle + beatmap.hitObjectSlider + beatmap.hitObjectSpinner)];
  634.  
  635. try (BufferedReader in = new BufferedReader(new FileReader(beatmap.getFile()))) {
  636. String line = in.readLine();
  637. while (line != null) {
  638. line = line.trim();
  639. if (!line.equals("[HitObjects]"))
  640. line = in.readLine();
  641. else
  642. break;
  643. }
  644. if (line == null) {
  645. Log.warn(String.format("No hit objects found in Beatmap '%s'.", beatmap.toString()));
  646. return;
  647. }
  648.  
  649. // combo info
  650. Color[] combo = beatmap.getComboColors();
  651. int comboIndex = 0; // color index
  652. int comboNumber = 1; // combo number
  653.  
  654. int objectIndex = 0;
  655. boolean first = true;
  656. while ((line = in.readLine()) != null && objectIndex < beatmap.objects.length) {
  657. line = line.trim();
  658. if (!isValidLine(line))
  659. continue;
  660. if (line.charAt(0) == '[')
  661. break;
  662.  
  663. // lines must have at minimum 5 parameters
  664. int tokenCount = line.length() - line.replace(",", "").length();
  665. if (tokenCount < 4)
  666. continue;
  667.  
  668. try {
  669. // create a new HitObject for each line
  670. HitObject hitObject = new HitObject(line);
  671.  
  672. // set combo info
  673. // - new combo: get next combo index, reset combo number
  674. // - else: maintain combo index, increase combo number
  675. if (hitObject.isNewCombo() || first) {
  676. int skip = (hitObject.isSpinner() ? 0 : 1) + hitObject.getComboSkip();
  677. for (int i = 0; i < skip; i++) {
  678. comboIndex = (comboIndex + 1) % combo.length;
  679. comboNumber = 1;
  680. }
  681. first = false;
  682. }
  683.  
  684. hitObject.setComboIndex(comboIndex);
  685. hitObject.setComboNumber(comboNumber++);
  686.  
  687. beatmap.objects[objectIndex++] = hitObject;
  688. } catch (Exception e) {
  689. Log.warn(String.format("Failed to read hit object '%s' for beatmap '%s'.",
  690. line, beatmap.toString()), e);
  691. }
  692. }
  693.  
  694. // check that all objects were parsed
  695. if (objectIndex != beatmap.objects.length)
  696. ErrorHandler.error(String.format("Parsed %d objects for beatmap '%s', %d objects expected.",
  697. objectIndex, beatmap.toString(), beatmap.objects.length), null, true);
  698. } catch (IOException e) {
  699. ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
  700. }
  701. }
  702.  
  703. /**
  704. * Returns false if the line is too short or commented.
  705. */
  706. private static boolean isValidLine(String line) {
  707. return (line.length() > 1 && !line.startsWith("//"));
  708. }
  709.  
  710. /**
  711. * Splits line into two strings: tag, value.
  712. * If no ':' character is present, null will be returned.
  713. */
  714. private static String[] tokenize(String line) {
  715. int index = line.indexOf(':');
  716. if (index == -1) {
  717. Log.debug(String.format("Failed to tokenize line: '%s'.", line));
  718. return null;
  719. }
  720.  
  721. String[] tokens = new String[2];
  722. tokens[0] = line.substring(0, index).trim();
  723. tokens[1] = line.substring(index + 1).trim();
  724. return tokens;
  725. }
  726.  
  727. /**
  728. * Returns the file extension of a file.
  729. * @param file the file name
  730. */
  731. public static String getExtension(String file) {
  732. int i = file.lastIndexOf('.');
  733. return (i != -1) ? file.substring(i + 1).toLowerCase() : "";
  734. }
  735.  
  736. /**
  737. * Returns the name of the current file being parsed, or null if none.
  738. */
  739. public static String getCurrentFileName() {
  740. if (status == Status.PARSING)
  741. return (currentFile != null) ? currentFile.getName() : null;
  742. else
  743. return (status == Status.NONE) ? null : "";
  744. }
  745.  
  746. /**
  747. * Returns the progress of file parsing, or -1 if not parsing.
  748. * @return the completion percent [0, 100] or -1
  749. */
  750. public static int getParserProgress() {
  751. if (currentDirectoryIndex == -1 || totalDirectories == -1)
  752. return -1;
  753.  
  754. return currentDirectoryIndex * 100 / totalDirectories;
  755. }
  756.  
  757. /**
  758. * Returns the current parser status.
  759. */
  760. public static Status getStatus() { return status; }
  761.  
  762. /**
  763. * Returns the String object in the database for the given String.
  764. * If none, insert the String into the database and return the original String.
  765. * @param s the string to retrieve
  766. * @return the string object
  767. */
  768. public static String getDBString(String s) {
  769. String DBString = stringdb.get(s);
  770. if (DBString == null) {
  771. stringdb.put(s, s);
  772. return s;
  773. } else
  774. return DBString;
  775. }
  776. }
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
Main.java:50: error: class BeatmapParser is public, should be declared in a file named BeatmapParser.java
public class BeatmapParser {
       ^
Main.java:21: error: cannot find symbol
import itdelatrisu.opsu.ErrorHandler;
                       ^
  symbol:   class ErrorHandler
  location: package itdelatrisu.opsu
Main.java:22: error: cannot find symbol
import itdelatrisu.opsu.Options;
                       ^
  symbol:   class Options
  location: package itdelatrisu.opsu
Main.java:23: error: cannot find symbol
import itdelatrisu.opsu.Utils;
                       ^
  symbol:   class Utils
  location: package itdelatrisu.opsu
Main.java:24: error: package itdelatrisu.opsu.db does not exist
import itdelatrisu.opsu.db.BeatmapDB;
                          ^
Main.java:25: error: package itdelatrisu.opsu.io does not exist
import itdelatrisu.opsu.io.MD5InputStreamWrapper;
                          ^
Main.java:44: error: package org.newdawn.slick does not exist
import org.newdawn.slick.Color;
                        ^
Main.java:45: error: package org.newdawn.slick.util does not exist
import org.newdawn.slick.util.Log;
                             ^
Main.java:101: error: cannot find symbol
	public static BeatmapSetNode parseDirectories(File[] dirs) {
	              ^
  symbol:   class BeatmapSetNode
  location: class BeatmapParser
Main.java:223: error: cannot find symbol
	private static Beatmap parseFile(File file, File dir, ArrayList<Beatmap> beatmaps, boolean parseObjects) {
	                                                                ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:223: error: cannot find symbol
	private static Beatmap parseFile(File file, File dir, ArrayList<Beatmap> beatmaps, boolean parseObjects) {
	               ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:631: error: cannot find symbol
	public static void parseHitObjects(Beatmap beatmap) {
	                                   ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:85: error: cannot find symbol
		BeatmapSetList.create();
		^
  symbol:   variable BeatmapSetList
  location: class BeatmapParser
Main.java:88: error: cannot find symbol
		if (Options.isWatchServiceEnabled())
		    ^
  symbol:   variable Options
  location: class BeatmapParser
Main.java:89: error: cannot find symbol
			BeatmapWatchService.create();
			^
  symbol:   variable BeatmapWatchService
  location: class BeatmapParser
Main.java:111: error: cannot find symbol
		Map<String, Long> map = BeatmapDB.getLastModifiedMap();
		                        ^
  symbol:   variable BeatmapDB
  location: class BeatmapParser
Main.java:114: error: cannot find symbol
		List<ArrayList<Beatmap>> allBeatmaps = new LinkedList<ArrayList<Beatmap>>();
		               ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:114: error: cannot find symbol
		List<ArrayList<Beatmap>> allBeatmaps = new LinkedList<ArrayList<Beatmap>>();
		                                                                ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:115: error: cannot find symbol
		List<Beatmap> cachedBeatmaps = new LinkedList<Beatmap>();  // loaded from database
		     ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:115: error: cannot find symbol
		List<Beatmap> cachedBeatmaps = new LinkedList<Beatmap>();  // loaded from database
		                                              ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:116: error: cannot find symbol
		List<Beatmap> parsedBeatmaps = new LinkedList<Beatmap>();  // loaded from parser
		     ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:116: error: cannot find symbol
		List<Beatmap> parsedBeatmaps = new LinkedList<Beatmap>();  // loaded from parser
		                                              ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:119: error: cannot find symbol
		BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null;
		^
  symbol:   class BeatmapWatchService
  location: class BeatmapParser
Main.java:119: error: cannot find symbol
		BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null;
		                          ^
  symbol:   variable Options
  location: class BeatmapParser
Main.java:119: error: cannot find symbol
		BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null;
		                                                             ^
  symbol:   variable BeatmapWatchService
  location: class BeatmapParser
Main.java:122: error: cannot find symbol
		BeatmapSetNode lastNode = null;
		^
  symbol:   class BeatmapSetNode
  location: class BeatmapParser
Main.java:139: error: cannot find symbol
			ArrayList<Beatmap> beatmaps = new ArrayList<Beatmap>();
			          ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:139: error: cannot find symbol
			ArrayList<Beatmap> beatmaps = new ArrayList<Beatmap>();
			                                            ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:151: error: cannot find symbol
							Beatmap beatmap = new Beatmap(file);
							^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:151: error: cannot find symbol
							Beatmap beatmap = new Beatmap(file);
							                      ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:156: error: cannot find symbol
							BeatmapDB.delete(dir.getName(), file.getName());
							^
  symbol:   variable BeatmapDB
  location: class BeatmapParser
Main.java:162: error: cannot find symbol
				Beatmap beatmap = parseFile(file, dir, beatmaps, false);
				^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:190: error: cannot find symbol
			BeatmapDB.load(cachedBeatmaps, BeatmapDB.LOAD_NONARRAY);
			                               ^
  symbol:   variable BeatmapDB
  location: class BeatmapParser
Main.java:190: error: cannot find symbol
			BeatmapDB.load(cachedBeatmaps, BeatmapDB.LOAD_NONARRAY);
			^
  symbol:   variable BeatmapDB
  location: class BeatmapParser
Main.java:194: error: cannot find symbol
		for (ArrayList<Beatmap> beatmaps : allBeatmaps) {
		               ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:196: error: cannot find symbol
			lastNode = BeatmapSetList.get().addSongGroup(beatmaps);
			           ^
  symbol:   variable BeatmapSetList
  location: class BeatmapParser
Main.java:205: error: cannot find symbol
			BeatmapDB.insert(parsedBeatmaps);
			^
  symbol:   variable BeatmapDB
  location: class BeatmapParser
Main.java:224: error: cannot find symbol
		Beatmap beatmap = new Beatmap(file);
		^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:224: error: cannot find symbol
		Beatmap beatmap = new Beatmap(file);
		                      ^
  symbol:   class Beatmap
  location: class BeatmapParser
Main.java:225: error: cannot find symbol
		beatmap.timingPoints = new ArrayList<TimingPoint>();
		                                     ^
  symbol:   class TimingPoint
  location: class BeatmapParser
Main.java:229: error: cannot find symbol
			MD5InputStreamWrapper md5stream = (!hasNoMD5Algorithm) ? new MD5InputStreamWrapper(bis) : null;
			^
  symbol:   class MD5InputStreamWrapper
  location: class BeatmapParser
Main.java:229: error: cannot find symbol
			MD5InputStreamWrapper md5stream = (!hasNoMD5Algorithm) ? new MD5InputStreamWrapper(bis) : null;
			                                                             ^
  symbol:   class MD5InputStreamWrapper
  location: class BeatmapParser
Main.java:272: error: cannot find symbol
										Log.error(String.format("Audio file '%s' not found in directory '%s'.", tokens[1], dir.getName()));
										^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:300: error: cannot find symbol
								if (beatmap.mode != Beatmap.MODE_OSU)
								                    ^
  symbol:   variable Beatmap
  location: class BeatmapParser
Main.java:305: error: cannot find symbol
								beatmap.letterboxInBreaks = Utils.parseBoolean(tokens[1]);
								                            ^
  symbol:   variable Utils
  location: class BeatmapParser
Main.java:308: error: cannot find symbol
								beatmap.widescreenStoryboard = Utils.parseBoolean(tokens[1]);
								                               ^
  symbol:   variable Utils
  location: class BeatmapParser
Main.java:311: error: cannot find symbol
								beatmap.epilepsyWarning = Utils.parseBoolean(tokens[1]);
								                          ^
  symbol:   variable Utils
  location: class BeatmapParser
Main.java:316: error: cannot find symbol
							Log.warn(String.format("Failed to read line '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:403: error: cannot find symbol
							Log.warn(String.format("Failed to read metadata '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:446: error: cannot find symbol
							Log.warn(String.format("Failed to read difficulty '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:475: error: cannot find symbol
								Log.warn(String.format("Failed to read break period '%s' for file '%s'.",
								^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:497: error: cannot find symbol
							TimingPoint timingPoint = new TimingPoint(line);
							^
  symbol:   class TimingPoint
  location: class BeatmapParser
Main.java:497: error: cannot find symbol
							TimingPoint timingPoint = new TimingPoint(line);
							                              ^
  symbol:   class TimingPoint
  location: class BeatmapParser
Main.java:512: error: cannot find symbol
							Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:519: error: cannot find symbol
					LinkedList<Color> colors = new LinkedList<Color>();
					           ^
  symbol:   class Color
  location: class BeatmapParser
Main.java:519: error: cannot find symbol
					LinkedList<Color> colors = new LinkedList<Color>();
					                                          ^
  symbol:   class Color
  location: class BeatmapParser
Main.java:530: error: cannot find symbol
							Color color = new Color(
							^
  symbol:   class Color
  location: class BeatmapParser
Main.java:530: error: cannot find symbol
							Color color = new Color(
							                  ^
  symbol:   class Color
  location: class BeatmapParser
Main.java:553: error: cannot find symbol
							Log.warn(String.format("Failed to read color '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:558: error: cannot find symbol
						beatmap.combo = colors.toArray(new Color[colors.size()]);
						                                   ^
  symbol:   class Color
  location: class BeatmapParser
Main.java:572: error: cannot find symbol
							if ((type & HitObject.TYPE_CIRCLE) > 0)
							            ^
  symbol:   variable HitObject
  location: class BeatmapParser
Main.java:574: error: cannot find symbol
							else if ((type & HitObject.TYPE_SLIDER) > 0)
							                 ^
  symbol:   variable HitObject
  location: class BeatmapParser
Main.java:579: error: cannot find symbol
							Log.warn(String.format("Failed to read hit object '%s' for file '%s'.",
							^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:586: error: cannot find symbol
						if ((type & HitObject.TYPE_SPINNER) > 0) {
						            ^
  symbol:   variable HitObject
  location: class BeatmapParser
Main.java:595: error: cannot find symbol
						Log.warn(String.format("Failed to read hit object end time '%s' for file '%s'.",
						^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:607: error: cannot find symbol
			ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false);
			^
  symbol:   variable ErrorHandler
  location: class BeatmapParser
Main.java:609: error: cannot find symbol
			ErrorHandler.error("Failed to get MD5 hash stream.", e, true);
			^
  symbol:   variable ErrorHandler
  location: class BeatmapParser
Main.java:635: error: cannot find symbol
		beatmap.objects = new HitObject[(beatmap.hitObjectCircle + beatmap.hitObjectSlider + beatmap.hitObjectSpinner)];
		                      ^
  symbol:   class HitObject
  location: class BeatmapParser
Main.java:647: error: cannot find symbol
				Log.warn(String.format("No hit objects found in Beatmap '%s'.", beatmap.toString()));
				^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:652: error: cannot find symbol
			Color[] combo = beatmap.getComboColors();
			^
  symbol:   class Color
  location: class BeatmapParser
Main.java:672: error: cannot find symbol
					HitObject hitObject = new HitObject(line);
					^
  symbol:   class HitObject
  location: class BeatmapParser
Main.java:672: error: cannot find symbol
					HitObject hitObject = new HitObject(line);
					                          ^
  symbol:   class HitObject
  location: class BeatmapParser
Main.java:691: error: cannot find symbol
					Log.warn(String.format("Failed to read hit object '%s' for beatmap '%s'.",
					^
  symbol:   variable Log
  location: class BeatmapParser
Main.java:698: error: cannot find symbol
				ErrorHandler.error(String.format("Parsed %d objects for beatmap '%s', %d objects expected.",
				^
  symbol:   variable ErrorHandler
  location: class BeatmapParser
Main.java:701: error: cannot find symbol
			ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
			^
  symbol:   variable ErrorHandler
  location: class BeatmapParser
Main.java:719: error: cannot find symbol
			Log.debug(String.format("Failed to tokenize line: '%s'.", line));
			^
  symbol:   variable Log
  location: class BeatmapParser
76 errors
stdout
Standard output is empty